From be3b0069fb8840cfb11e787e4c59baa0a28f671b Mon Sep 17 00:00:00 2001 From: "Lucas Perais (lpe)" Date: Tue, 12 Sep 2017 09:46:31 +0200 Subject: [PATCH 01/22] [FIX] account_extra_reports: empty partner name in sales/purchase journal Before this commit, since a partner's name can be empty, a traceback was thrown while opening the report in such a case. Now, if the partner's name is falsy we don't try to do operations on it OPW 769835 closes #19400 --- addons/account_extra_reports/views/report_journal.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/account_extra_reports/views/report_journal.xml b/addons/account_extra_reports/views/report_journal.xml index b9a22669889..c9481d0c095 100644 --- a/addons/account_extra_reports/views/report_journal.xml +++ b/addons/account_extra_reports/views/report_journal.xml @@ -49,7 +49,7 @@ - + From 4e799fed8ee24fb30f08e9c97d38625aa42d1c7c Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Thu, 14 Sep 2017 14:46:02 +0200 Subject: [PATCH 02/22] [FIX] crm: expected closing Backport of commit abea8d3513e0d6383088c738523425be0cc04dc7 opw-771843 --- addons/crm/crm_lead.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index b8902cb736d..465ab6540eb 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -1158,7 +1158,7 @@ Update your business card, phone book, social media,... Send an email right now res['activity']['today'] += 1 if date_action >= date.today() and date_action <= date.today() + timedelta(days=7): res['activity']['next_7_days'] += 1 - if date_action < date.today(): + if date_action < date.today() and not opp['date_closed']: res['activity']['overdue'] += 1 # Won in Opportunities From c14eb9ae9e29977be27bc0370d7662a6acd6ab9a Mon Sep 17 00:00:00 2001 From: Odoo Translation Bot Date: Sun, 17 Sep 2017 00:29:20 +0200 Subject: [PATCH 03/22] [I18N] Update translation terms from Transifex --- addons/crm/i18n/pl.po | 14 +++++++------- addons/hr_timesheet/i18n/pl.po | 4 ++-- addons/project/i18n/hr.po | 4 ++-- addons/report/i18n/zh_CN.po | 12 ++++++------ addons/sale_stock/i18n/hr.po | 4 ++-- addons/sale_timesheet/i18n/pl.po | 4 ++-- addons/website/i18n/de.po | 4 ++-- openerp/addons/base/i18n/hr.po | 4 ++-- openerp/addons/base/i18n/tr.po | 14 +++++++------- 9 files changed, 32 insertions(+), 32 deletions(-) diff --git a/addons/crm/i18n/pl.po b/addons/crm/i18n/pl.po index 605ba4d065a..9ab266a33db 100644 --- a/addons/crm/i18n/pl.po +++ b/addons/crm/i18n/pl.po @@ -12,7 +12,7 @@ msgstr "" "Project-Id-Version: Odoo 9.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-09-22 13:15+0000\n" -"PO-Revision-Date: 2017-07-19 08:59+0000\n" +"PO-Revision-Date: 2017-09-15 12:47+0000\n" "Last-Translator: Grzegorz Grzelak \n" "Language-Team: Polish (http://www.transifex.com/odoo/odoo-9/language/pl/)\n" "MIME-Version: 1.0\n" @@ -366,7 +366,7 @@ msgstr "Na przykład: Etapy negocjacji" #. module: crm #: model:ir.ui.view,arch_db:crm.crm_planner msgid "Example: Qualified stage" -msgstr "" +msgstr "Na przykład: Etap kwalifikacji" #. module: crm #: model:ir.ui.view,arch_db:crm.crm_planner @@ -430,7 +430,7 @@ msgstr "Propozycje:" #. module: crm #: model:ir.ui.view,arch_db:crm.crm_planner msgid "Qualified:" -msgstr "" +msgstr "Kwalifikacje:" #. module: crm #: model:ir.ui.view,arch_db:crm.crm_planner @@ -2059,7 +2059,7 @@ msgstr "Sygnał utworzony" #: model:ir.ui.view,arch_db:crm.crm_lead_tag_form #: model:ir.ui.view,arch_db:crm.crm_lead_tag_tree msgid "Lead Tags" -msgstr "" +msgstr "Tagi sygnałów" #. module: crm #: model:ir.model,name:crm.model_crm_lead2opportunity_partner @@ -2225,13 +2225,13 @@ msgstr "Porażka" #: model:ir.ui.view,arch_db:crm.crm_opportunity_report_view_search #: model:ir.ui.view,arch_db:crm.view_crm_case_opportunities_filter msgid "Lost Reason" -msgstr "" +msgstr "Przyczyna utraty" #. module: crm #: model:ir.actions.act_window,name:crm.crm_lost_reason_action #: model:ir.ui.menu,name:crm.menu_crm_lost_reason msgid "Lost Reasons" -msgstr "" +msgstr "Powody utraty" #. module: crm #: selection:crm.lead,priority:0 selection:crm.opportunity.report,priority:0 @@ -3018,7 +3018,7 @@ msgstr "" #: code:addons/crm/sales_team.py:23 #, python-format msgid "Qualified" -msgstr "" +msgstr "Kwalifikacje" #. module: crm #: model:ir.model.fields,field_description:crm.field_crm_lead_priority diff --git a/addons/hr_timesheet/i18n/pl.po b/addons/hr_timesheet/i18n/pl.po index 9163e777e58..4c5e71999b1 100644 --- a/addons/hr_timesheet/i18n/pl.po +++ b/addons/hr_timesheet/i18n/pl.po @@ -10,14 +10,14 @@ msgstr "" "Project-Id-Version: Odoo 9.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-09-14 10:26+0000\n" -"PO-Revision-Date: 2016-10-13 07:51+0000\n" +"PO-Revision-Date: 2017-09-15 12:43+0000\n" "Last-Translator: Grzegorz Grzelak \n" "Language-Team: Polish (http://www.transifex.com/odoo/odoo-9/language/pl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" "Language: pl\n" -"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n" #. module: hr_timesheet #: model:ir.ui.view,arch_db:hr_timesheet.hr_timesheet_line_form diff --git a/addons/project/i18n/hr.po b/addons/project/i18n/hr.po index d6a3d5cf0f1..0d4793bf87b 100644 --- a/addons/project/i18n/hr.po +++ b/addons/project/i18n/hr.po @@ -10,7 +10,7 @@ msgstr "" "Project-Id-Version: Odoo 9.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-10-09 09:17+0000\n" -"PO-Revision-Date: 2017-09-01 09:50+0000\n" +"PO-Revision-Date: 2017-09-11 08:53+0000\n" "Last-Translator: Bole \n" "Language-Team: Croatian (http://www.transifex.com/odoo/odoo-9/language/hr/)\n" "MIME-Version: 1.0\n" @@ -1679,7 +1679,7 @@ msgstr "Predviđanja" #. module: project #: model:ir.model.fields,field_description:project.field_project_config_settings_module_project_forecast msgid "Forecasts, planning and Gantt charts" -msgstr "" +msgstr "Predviđanja, planiranje i Ganttov dijagram" #. module: project #: model:ir.ui.menu,name:project.menu_tasks_config diff --git a/addons/report/i18n/zh_CN.po b/addons/report/i18n/zh_CN.po index 1ccf0ccdfee..9729c22a518 100644 --- a/addons/report/i18n/zh_CN.po +++ b/addons/report/i18n/zh_CN.po @@ -3,19 +3,19 @@ # * report # # Translators: -# Jeffery Chen Fan , 2015 -# Jeffery Chen Fan , 2015-2016 +# Jeffery CHEN , 2015 +# Jeffery CHEN , 2015-2016 # liAnGjiA , 2015 # liyes , 2015 -# zhanghao , 2015 +# 卓忆科技 , 2015 # liAnGjiA , 2015 msgid "" msgstr "" "Project-Id-Version: Odoo 9.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-12-07 10:39+0000\n" -"PO-Revision-Date: 2016-12-08 08:38+0000\n" -"Last-Translator: Martin Trigaux\n" +"PO-Revision-Date: 2017-09-13 05:57+0000\n" +"Last-Translator: liAnGjiA \n" "Language-Team: Chinese (China) (http://www.transifex.com/odoo/odoo-9/language/zh_CN/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -474,7 +474,7 @@ msgstr "在这个系统里没找到Wkhtmltopdf。\n 报表会被显示为html。 #: code:addons/report/models/report.py:164 #, python-format msgid "Unable to find Wkhtmltopdf on this system. The PDF can not be created." -msgstr "" +msgstr "未找到 WKhtmltopdf 系统,无法创建 PDF。" #. module: report #: model:ir.ui.view,arch_db:report.external_layout_footer diff --git a/addons/sale_stock/i18n/hr.po b/addons/sale_stock/i18n/hr.po index cee1c0862f7..a6902408b29 100644 --- a/addons/sale_stock/i18n/hr.po +++ b/addons/sale_stock/i18n/hr.po @@ -11,7 +11,7 @@ msgstr "" "Project-Id-Version: Odoo 9.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-10-01 06:27+0000\n" -"PO-Revision-Date: 2017-03-01 14:27+0000\n" +"PO-Revision-Date: 2017-09-11 13:03+0000\n" "Last-Translator: Bole \n" "Language-Team: Croatian (http://www.transifex.com/odoo/odoo-9/language/hr/)\n" "MIME-Version: 1.0\n" @@ -221,7 +221,7 @@ msgstr "Statistika prodajnih naloga" #. module: sale_stock #: model:ir.model.fields,field_description:sale_stock.field_res_company_security_lead msgid "Sales Safety Days" -msgstr "" +msgstr "Sigurnosni dani prodaje" #. module: sale_stock #: model:ir.model.fields,field_description:sale_stock.field_stock_location_route_sale_selectable diff --git a/addons/sale_timesheet/i18n/pl.po b/addons/sale_timesheet/i18n/pl.po index 49d1fffb14d..6168136ba11 100644 --- a/addons/sale_timesheet/i18n/pl.po +++ b/addons/sale_timesheet/i18n/pl.po @@ -9,14 +9,14 @@ msgstr "" "Project-Id-Version: Odoo 9.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-09-14 10:28+0000\n" -"PO-Revision-Date: 2016-10-14 15:32+0000\n" +"PO-Revision-Date: 2017-09-15 13:04+0000\n" "Last-Translator: zbik2607 \n" "Language-Team: Polish (http://www.transifex.com/odoo/odoo-9/language/pl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" "Language: pl\n" -"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n" #. module: sale_timesheet #: model:ir.model,name:sale_timesheet.model_account_analytic_line diff --git a/addons/website/i18n/de.po b/addons/website/i18n/de.po index 3f9139efbad..cfa7ebfb523 100644 --- a/addons/website/i18n/de.po +++ b/addons/website/i18n/de.po @@ -13,7 +13,7 @@ msgstr "" "Project-Id-Version: Odoo 9.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-10-09 09:18+0000\n" -"PO-Revision-Date: 2017-03-29 15:18+0000\n" +"PO-Revision-Date: 2017-09-16 17:49+0000\n" "Last-Translator: Ermin Trevisan \n" "Language-Team: German (http://www.transifex.com/odoo/odoo-9/language/de/)\n" "MIME-Version: 1.0\n" @@ -2737,7 +2737,7 @@ msgstr "Ok" #: code:addons/website/static/src/xml/website.backend.xml:5 #, python-format msgid "On Website" -msgstr "" +msgstr "Auf Website" #. module: website #: model:ir.ui.view,arch_db:website.website_planner diff --git a/openerp/addons/base/i18n/hr.po b/openerp/addons/base/i18n/hr.po index 655c63f2d54..b3bc26e5833 100644 --- a/openerp/addons/base/i18n/hr.po +++ b/openerp/addons/base/i18n/hr.po @@ -16,7 +16,7 @@ msgstr "" "Project-Id-Version: Odoo 9.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-06-22 07:25+0000\n" -"PO-Revision-Date: 2017-09-01 08:36+0000\n" +"PO-Revision-Date: 2017-09-12 09:24+0000\n" "Last-Translator: Martin Trigaux\n" "Language-Team: Croatian (http://www.transifex.com/odoo/odoo-9/language/hr/)\n" "MIME-Version: 1.0\n" @@ -15857,7 +15857,7 @@ msgstr "" msgid "" "The internal user that is in charge of communicating with this contact if " "any." -msgstr "" +msgstr "Korisnik koji je zadužen za komunikaciju za ovim kontaktom, ukoliko postoji." #. module: base #: model:ir.model.fields,help:base.field_ir_model_inherited_model_ids diff --git a/openerp/addons/base/i18n/tr.po b/openerp/addons/base/i18n/tr.po index b7fd96415e6..182ca7bcac2 100644 --- a/openerp/addons/base/i18n/tr.po +++ b/openerp/addons/base/i18n/tr.po @@ -18,7 +18,7 @@ msgstr "" "Project-Id-Version: Odoo 9.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-06-22 07:25+0000\n" -"PO-Revision-Date: 2017-08-17 01:06+0000\n" +"PO-Revision-Date: 2017-09-16 14:30+0000\n" "Last-Translator: Martin Trigaux\n" "Language-Team: Turkish (http://www.transifex.com/odoo/odoo-9/language/tr/)\n" "MIME-Version: 1.0\n" @@ -423,7 +423,7 @@ msgstr "" msgid "" "\n" " Use Plaid.com to retrieve bank statements" -msgstr "" +msgstr "\n Banka hesap özetini almak için Plaid.com servisi kullan." #. module: base #: model:ir.module.module,description:base.module_account_plaid @@ -431,7 +431,7 @@ msgid "" "\n" " Use Plaid.com to retrieve bank statements.\n" " " -msgstr "" +msgstr "\n Banka hesap özetini almak için Plaid.com servisi kullan..\n " #. module: base #: model:ir.module.module,description:base.module_l10n_multilang @@ -4889,7 +4889,7 @@ msgstr "Analitik Varsayılan Hesaplar" #. module: base #: model:ir.module.module,shortdesc:base.module_account_bank_statement_import msgid "Account Bank Statement Import" -msgstr "" +msgstr "Banka Hesap Özeti İçe Aktarımı" #. module: base #: model:ir.module.category,name:base.module_category_localization_account_charts @@ -9324,7 +9324,7 @@ msgstr "Jenerik Modüller" msgid "" "Generic Wizard to Import Bank Statements. Includes the import of files in " ".OFX format" -msgstr "" +msgstr "Banka Hesap Özeti İçe Aktarımı Sihirbazı" #. module: base #: model:ir.module.module,description:base.module_website_form @@ -10185,12 +10185,12 @@ msgstr "İçe Aktar / Dışa Aktar" #. module: base #: model:ir.module.module,shortdesc:base.module_account_bank_statement_import_ofx msgid "Import OFX Bank Statement" -msgstr "" +msgstr "QFX Formatında Banka Hesap Özeti İçe Aktarımı" #. module: base #: model:ir.module.module,shortdesc:base.module_account_bank_statement_import_qif msgid "Import QIF Bank Statement" -msgstr "" +msgstr "QIF Formatında Banka Hesap Özeti İçe Aktarımı" #. module: base #: model:ir.actions.act_window,name:base.action_view_base_import_language From 875dd9221f80ea1b60cd9ee8a005d71246734a2b Mon Sep 17 00:00:00 2001 From: Goffin Simon Date: Mon, 18 Sep 2017 17:06:13 +0200 Subject: [PATCH 04/22] [FIX] account, website_portal_sale: _generate_and_pay_invoice in other currency When paying in other currency that the one defined in the company, it generates a payment which has to be in the currency of the transaction. Otherwise it will generate an invoice which won't be completely paid. opw:769278 --- addons/account/models/account_invoice.py | 8 ++++++-- addons/website_portal_sale/models/payment.py | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index 56b95ab8feb..c56ea11333b 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -1113,7 +1113,7 @@ class AccountInvoice(models.Model): if self.origin: communication = '%s (%s)' % (communication, self.origin) - payment = self.env['account.payment'].create({ + payment_vals = { 'invoice_ids': [(6, 0, self.ids)], 'amount': pay_amount or self.residual, 'payment_date': date or fields.Date.context_today(self), @@ -1125,7 +1125,11 @@ class AccountInvoice(models.Model): 'payment_method_id': payment_method.id, 'payment_difference_handling': writeoff_acc and 'reconcile' or 'open', 'writeoff_account_id': writeoff_acc and writeoff_acc.id or False, - }) + } + if self.env.context.get('tx_currency_id'): + payment_vals['currency_id'] = self.env.context.get('tx_currency_id') + + payment = self.env['account.payment'].create(payment_vals) payment.post() return True diff --git a/addons/website_portal_sale/models/payment.py b/addons/website_portal_sale/models/payment.py index 1005250807f..7aa838149bd 100644 --- a/addons/website_portal_sale/models/payment.py +++ b/addons/website_portal_sale/models/payment.py @@ -29,7 +29,7 @@ class PaymentTransaction(models.Model): created_invoice.action_invoice_open() if tx.acquirer_id.journal_id: - created_invoice.pay_and_reconcile(tx.acquirer_id.journal_id, pay_amount=created_invoice.amount_total) + created_invoice.with_context(tx_currency_id=tx.currency_id.id).pay_and_reconcile(tx.acquirer_id.journal_id, pay_amount=created_invoice.amount_total) if created_invoice.payment_ids: created_invoice.payment_ids[0].payment_transaction_id = tx else: From 3678cffcf940f699b29630c3ac3f5427a3cdf9f7 Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Mon, 18 Sep 2017 16:07:50 +0530 Subject: [PATCH 05/22] [FIX] account: due date On an invoice: - Select a partner with payment term defined => due date D1 is set - Change to a partner withour payment term defined The due date D1 is kept. opw-772063 --- addons/account/models/account_invoice.py | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index c56ea11333b..8d73a1f5c85 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -505,6 +505,7 @@ class AccountInvoice(models.Model): self.account_id = account_id self.payment_term_id = payment_term_id + self.date_due = False self.fiscal_position_id = fiscal_position if type in ('in_invoice', 'out_refund'): From a79d83e436e5e965d59663d4a06a0f8a62d8f694 Mon Sep 17 00:00:00 2001 From: qsm-odoo Date: Tue, 19 Sep 2017 12:30:15 +0200 Subject: [PATCH 06/22] [FIX] mrp, stock: picking type automatic colors /!\ Should not be forward-ported in saas-18 Fix bug introduced by https://github.com/odoo/odoo/commit/e59c1b49c2d9729e5f7576e1c205701a340f9396: When creating picking types, a color is automatically assigned. This color is chosen to be the first available one, or by default 1 (since mentioned commit, as '1' would be the default gray used in kanban headers). Unfortunately, this was expressed as `color = first or 1`... which will always be 1 since the very first available color is always 0. --- addons/mrp/models/stock_warehouse.py | 2 +- addons/stock/models/stock_warehouse.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/addons/mrp/models/stock_warehouse.py b/addons/mrp/models/stock_warehouse.py index d2e9f28e7b3..01691a27b70 100644 --- a/addons/mrp/models/stock_warehouse.py +++ b/addons/mrp/models/stock_warehouse.py @@ -58,7 +58,7 @@ class StockWarehouse(models.Model): wh_stock_loc = warehouse.lot_stock_id seq = seq_obj.search([('code', '=', 'mrp.production')], limit=1) other_pick_type = picking_type_obj.search([('warehouse_id', '=', warehouse.id)], order = 'sequence desc', limit=1) - color = other_pick_type and other_pick_type.color or 1 + color = other_pick_type.color if other_pick_type else 1 max_sequence = other_pick_type and other_pick_type.sequence or 0 manu_type = picking_type_obj.create({ 'name': _('Manufacturing'), diff --git a/addons/stock/models/stock_warehouse.py b/addons/stock/models/stock_warehouse.py index bb4916e30de..a4c7876cb87 100644 --- a/addons/stock/models/stock_warehouse.py +++ b/addons/stock/models/stock_warehouse.py @@ -188,8 +188,8 @@ class Warehouse(models.Model): # choose the next available color for the operation types of this warehouse all_used_colors = [res['color'] for res in PickingType.search_read([('warehouse_id', '!=', False), ('color', '!=', False)], ['color'], order='color')] - available_colors = [zef for zef in [0, 3, 4, 5, 6, 7, 8, 1, 2] if zef not in all_used_colors] - color = available_colors and available_colors[0] or 1 + available_colors = [zef for zef in [1, 3, 4, 5, 6, 7, 8, 0, 2] if zef not in all_used_colors] + color = available_colors[0] if available_colors else 1 # suit for each warehouse: reception, internal, pick, pack, ship max_sequence = PickingType.search_read([('sequence', '!=', False)], ['sequence'], limit=1, order='sequence desc') From 90a253bb72ab142df27dc0ca0de2269acbb6ce69 Mon Sep 17 00:00:00 2001 From: Deep Patel Date: Thu, 3 Aug 2017 17:46:40 +0530 Subject: [PATCH 07/22] [FIX] web: Form view: make 'Edit' button bounce in readonly ... when the form is clicked. This commit restores the behavior of saas-15, before the new views. --- .../static/src/js/views/form/form_renderer.js | 11 +++++++ addons/web/static/tests/views/form_tests.js | 32 +++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/addons/web/static/src/js/views/form/form_renderer.js b/addons/web/static/src/js/views/form/form_renderer.js index 0b00bcd1971..ab491adc9b1 100644 --- a/addons/web/static/src/js/views/form/form_renderer.js +++ b/addons/web/static/src/js/views/form/form_renderer.js @@ -12,6 +12,7 @@ var FormRenderer = BasicRenderer.extend({ className: "o_form_view", events: _.extend({}, BasicRenderer.prototype.events, { 'click .o_notification_box .oe_field_translate': '_onTranslate', + 'click .oe_title, .o_group': '_onClick', }), /** * @override @@ -836,6 +837,16 @@ var FormRenderer = BasicRenderer.extend({ // Handlers //-------------------------------------------------------------------------- + /** + * Makes the Edit button bounce in readonly + * + * @private + */ + _onClick: function () { + if (this.mode === 'readonly') { + this.trigger_up('bounce_edit'); + } + }, /** * @override * @private diff --git a/addons/web/static/tests/views/form_tests.js b/addons/web/static/tests/views/form_tests.js index 4dcdede75d1..2b4c70a9399 100644 --- a/addons/web/static/tests/views/form_tests.js +++ b/addons/web/static/tests/views/form_tests.js @@ -5888,5 +5888,37 @@ QUnit.module('Views', { "table is now sorted"); form.destroy(); }); + + QUnit.test('bounce edit button in readonly mode', function (assert) { + assert.expect(3); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '
' + + '' + + '
' + + '
', + res_id: 1, + intercepts: { + bounce_edit: function() { + assert.step('bounce'); + }, + }, + }); + + // in readonly + form.$('[name="display_name"]').click(); + assert.verifySteps(['bounce']); + + // in edit + form.$buttons.find('.o_form_button_edit').click(); + form.$('[name="display_name"]').click(); + assert.verifySteps(['bounce']); + + form.destroy(); + }); }); }); From 73bec3d60feaced7d285ad062021f7703fc9e31a Mon Sep 17 00:00:00 2001 From: Cedric Snauwaert Date: Tue, 19 Sep 2017 12:34:54 +0200 Subject: [PATCH 08/22] [FIX] account: reconciliation, error when changing partner Traceback by pressing enter when changing partner which should not occur as keyUp event is in place for other M2O fields and not for the partner. In some case, if the line is balanced (marked as reconciled) and we change the user for one that does not have any move lines, we removed the selected proposition but we did not changed the line balance, resulting in a widget that is marked as inactive and no way to do something with it. --- .../js/reconciliation/reconciliation_model.js | 1 + .../js/reconciliation/reconciliation_renderer.js | 4 ++++ .../account/static/tests/reconciliation_tests.js | 16 +++++++++++++++- 3 files changed, 20 insertions(+), 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 9caea6d8712..1d86c058e09 100644 --- a/addons/account/static/src/js/reconciliation/reconciliation_model.js +++ b/addons/account/static/src/js/reconciliation/reconciliation_model.js @@ -235,6 +235,7 @@ var StatementModel = BasicModel.extend({ return $.when(partner && this._changePartner(handle, partner.id)) .then(function() { line.reconciliation_proposition = []; + self._computeLine(line); return self.changeMode(handle, 'match'); }) .then(function () { diff --git a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js index ff0abf316ec..d9aa12967e1 100644 --- a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js +++ b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js @@ -600,6 +600,10 @@ var LineRenderer = Widget.extend(FieldManagerMixin, { * @param {keyup event} event */ _onInputKeyup: function (event) { + var target_partner_id = $(event.target).parents('[name="partner_id"]'); + if (target_partner_id.length === 1) { + return; + } if(event.keyCode === 13) { if (_.findWhere(this.model.lines, {mode: 'create'}).balance.amount) { this._onCreateProposition(); diff --git a/addons/account/static/tests/reconciliation_tests.js b/addons/account/static/tests/reconciliation_tests.js index 27f327febb1..88d9a7a0f78 100644 --- a/addons/account/static/tests/reconciliation_tests.js +++ b/addons/account/static/tests/reconciliation_tests.js @@ -741,7 +741,7 @@ QUnit.module('account', { }); QUnit.test('Reconciliation change partner', function (assert) { - assert.expect(13); + assert.expect(17); var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options); @@ -759,6 +759,20 @@ QUnit.module('account', { var widget = clientAction.widgets[0]; assert.strictEqual(widget.$('.o_input_dropdown input').val(), "Agrolait", "the partner many2one should display agrolait"); assert.strictEqual(widget.$('.match table tr').length, 2, "agrolait should have 2 propositions for reconciliation"); + + // Adding the two propositions + // This is in order to try that after changing partner the propositions are emptied + widget.$('.match .cell_account_code:first').trigger('click'); + widget.$('.match .cell_account_code:first').trigger('click'); + assert.strictEqual(widget.$('.accounting_view tbody tr').length, 2, "Both proposition should be selected"); + + // Similate changing partner to one that does not have propositions to see if create mode is open after + widget.$('.o_input_dropdown input').trigger('click'); + $('.ui-autocomplete .ui-menu-item a:contains(partner 1)').trigger('mouseenter').trigger('click'); + clientAction._onAction({target: widget, name: 'change_partner', data: {data: {display_name: 'partner 1', id: 1}}, stopped: false}); + assert.strictEqual(widget.$('.o_input_dropdown input').val(), "partner 1", "the partner many2one should display partner 1"); + assert.strictEqual(widget.$('.match table tr.mv_line').length, 0, "partner 1 should have 0 propositions for reconciliation"); + assert.strictEqual(widget.$el.data('mode'), 'create', "widget should be in create mode"); // Simulate changing partner widget.$('.o_input_dropdown input').trigger('click'); From 4e9b5e8f23edf1a6f137277397a1d612f6ad7251 Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Wed, 6 Sep 2017 15:26:49 +0200 Subject: [PATCH 09/22] [FIX] bus: compatibility with Odoo's PAAS external long polling handler Only start the bus events dispatcher when needed (lazy starting). On Odoo PAAS it's never started because the platform has its own event dispatcher. Cherry-pick of rev. a2ed3d3d5bdb6025a1ba14ad557a115a86413e65 --- addons/bus/models/bus.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/addons/bus/models/bus.py b/addons/bus/models/bus.py index 466592505a9..645b67d2607 100644 --- a/addons/bus/models/bus.py +++ b/addons/bus/models/bus.py @@ -108,6 +108,7 @@ class ImBus(models.Model): class ImDispatch(object): def __init__(self): self.channels = {} + self.started = False def poll(self, dbname, channels, last, options=None, timeout=TIMEOUT): if options is None: @@ -129,6 +130,10 @@ class ImDispatch(object): notifications = env['bus.bus'].poll(channels, last, options) # or wait for future ones if not notifications: + if not self.started: + # Lazy start of events listener + self.start() + event = self.Event() for channel in channels: self.channels.setdefault(hashable(channel), []).append(event) @@ -178,15 +183,16 @@ class ImDispatch(object): import gevent self.Event = gevent.event.Event gevent.spawn(self.run) - elif odoo.multi_process: - # disabled in prefork mode - return else: # threaded mode self.Event = threading.Event t = threading.Thread(name="%s.Bus" % __name__, target=self.run) t.daemon = True t.start() + self.started = True return self -dispatch = ImDispatch().start() +dispatch = None +if not odoo.multi_process or odoo.evented: + # We only use the event dispatcher in threaded and gevent mode + dispatch = ImDispatch() From f234376034566fb6579306cfe9bb9cd812fd507c Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Wed, 6 Sep 2017 17:01:33 +0200 Subject: [PATCH 10/22] [FIX] bus: compatibility with Odoo's PAAS external long polling handler Add a `peek` option in the bus event dispatcher polling in order to let an external event dispatcher fetch the notifications and their corresponding channels. Cherry-pick of rev. f2f99dc091947c0799b17c767a6ad923cd535025 --- addons/bus/models/bus.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/addons/bus/models/bus.py b/addons/bus/models/bus.py index 645b67d2607..e58bc8f0276 100644 --- a/addons/bus/models/bus.py +++ b/addons/bus/models/bus.py @@ -128,6 +128,11 @@ class ImDispatch(object): with registry.cursor() as cr: env = api.Environment(cr, SUPERUSER_ID, {}) notifications = env['bus.bus'].poll(channels, last, options) + + # immediatly returns in peek mode + if options.get('peek'): + return dict(notifications=notifications, channels=channels) + # or wait for future ones if not notifications: if not self.started: From 35441cfd62cc1aa7ad5507d5dc8c69218c36bc82 Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Thu, 20 Oct 2016 09:10:58 +0200 Subject: [PATCH 11/22] [IMP] doc: add test classes documentation Closes #13902 --- doc/reference/testing.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/reference/testing.rst b/doc/reference/testing.rst index 9523d732e0f..794b6e45b18 100644 --- a/doc/reference/testing.rst +++ b/doc/reference/testing.rst @@ -48,6 +48,11 @@ related to testing Odoo content (modules, mainly): .. autoclass:: openerp.tests.common.SingleTransactionCase :members: browse_ref, ref +.. autoclass:: openerp.tests.common.SavepointCase + +.. autoclass:: openerp.tests.common.HttpCase + :members: browse_ref, ref, url_open, phantom_js + By default, tests are run once right after the corresponding module has been installed. Test cases can also be configured to run after all modules have been installed, and not run right after the module installation: From e34f6aaa26f022762c27ee62666e81e42eb1b8c5 Mon Sep 17 00:00:00 2001 From: "Laurent Mignon (ACSONE)" Date: Thu, 14 Sep 2017 12:16:32 +0200 Subject: [PATCH 12/22] [FIX] stock: procurements must be confirmed with the right company The method _procure_orderpoint_confirm in stock/procurement is calld in sudo() by run_scheduler. Even if this method receives the company as parameter, the company is not enforced for all the processes trigerred into the method. i.e. If a PO is generated by the run of the procurements the sequence used is the one of the admin's company and not the one for the given company. To enforce the company to use, we force the company into the context if it's not the same as the user's company. opw:771851 --- addons/stock/models/procurement.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/addons/stock/models/procurement.py b/addons/stock/models/procurement.py index 836d72875db..aaf5d34474e 100644 --- a/addons/stock/models/procurement.py +++ b/addons/stock/models/procurement.py @@ -295,6 +295,13 @@ class ProcurementOrder(models.Model): 1000 orderpoints. This is appropriate for batch jobs only. """ + if company_id and self.env.user.company_id.id != company_id: + # To ensure that the company_id is taken into account for + # all the processes triggered by this method + # i.e. If a PO is generated by the run of the procurements the + # sequence to use is the one for the specified company not the + # one of the user's company + self = self.with_context(company_id=company_id, force_company=company_id) OrderPoint = self.env['stock.warehouse.orderpoint'] domain = self._get_orderpoint_domain(company_id=company_id) From 6252c21000ad5c4c6d1eb3d5623393d267e8b70d Mon Sep 17 00:00:00 2001 From: Nicolas Lempereur Date: Tue, 5 Sep 2017 10:03:52 +0200 Subject: [PATCH 13/22] [FIX] web: modifiers computed when x2many ready Modifiers (eg. required, readonly, ... on a field) are computed after a record has been changed. When loading a record they could be computed based on x2many in a inconsistent state. eg. on res.partner form, by defaut the email is required depending on: [('user_ids','!=', [])] but when switching from one record to another this was computed wrongly based on the difference between the two records. This change make the x2many when loading a record ready to be used as soon as it is set to solve this issue. forward-port: not needed after saas-15 thanks to framework views rewrite fix #18239 opw-749483 closes #18815 --- addons/web/static/src/js/views/form_common.js | 6 ++++++ addons/web/static/src/js/views/form_relational_widgets.js | 8 +++++++- addons/web/static/src/js/views/form_view.js | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/addons/web/static/src/js/views/form_common.js b/addons/web/static/src/js/views/form_common.js index 638327c8a3a..16e8c088368 100644 --- a/addons/web/static/src/js/views/form_common.js +++ b/addons/web/static/src/js/views/form_common.js @@ -729,6 +729,12 @@ var AbstractField = FormWidget.extend(FieldInterface, { set_value: function(value_) { this.set({'value': value_}); }, + /** + Method to set value of a field when loading a record + */ + set_value_from_record: function(record) { + this.set_value.call(this, record[this.name] || false); + }, get_value: function() { return this.get('value'); }, diff --git a/addons/web/static/src/js/views/form_relational_widgets.js b/addons/web/static/src/js/views/form_relational_widgets.js index e8e9c450c3c..f6e18384c20 100644 --- a/addons/web/static/src/js/views/form_relational_widgets.js +++ b/addons/web/static/src/js/views/form_relational_widgets.js @@ -453,12 +453,18 @@ var AbstractManyField = common.AbstractField.extend({ }); }, - _on_load_record: function (record) { + set_value_from_record: function (record) { + this._super.apply(this, arguments); + // we want to update starting_ids straight away so the value can be used as soon as + // possible without inconsistency this.starting_ids = []; // don't set starting_ids for the new record if (record.id && record[this.name] && (!isNaN(record.id) || record.id.indexOf(this.dataset.virtual_id_prefix) === -1)) { this.starting_ids = this.get('value').slice(); } + }, + + _on_load_record: function (record) { this.trigger("load_record", record); }, diff --git a/addons/web/static/src/js/views/form_view.js b/addons/web/static/src/js/views/form_view.js index 1e93a2954c0..76117111eeb 100644 --- a/addons/web/static/src/js/views/form_view.js +++ b/addons/web/static/src/js/views/form_view.js @@ -308,7 +308,7 @@ var FormView = View.extend(common.FieldManagerMixin, { _(this.fields).each(function (field, f) { field._dirty_flag = false; field._inhibit_on_change_flag = true; - var result = field.set_value(self.datarecord[f] || false); + var result = field.set_value_from_record(self.datarecord); field._inhibit_on_change_flag = false; set_values.push(result); }); From 9f8efb9d164a6995299c9e76926effc2bb3aae6d Mon Sep 17 00:00:00 2001 From: Deep Patel Date: Tue, 19 Sep 2017 16:11:19 +0200 Subject: [PATCH 14/22] [FIX] web: editable list: don't sort rows in edition This commit disables the sort feature when a row is being edited, like it was in saas-15, because it didn't work correctly anyway. --- .../js/views/list/list_editable_renderer.js | 11 ++++++ addons/web/static/tests/views/list_tests.js | 34 +++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/addons/web/static/src/js/views/list/list_editable_renderer.js b/addons/web/static/src/js/views/list/list_editable_renderer.js index 7984ebf21e1..b101405e957 100644 --- a/addons/web/static/src/js/views/list/list_editable_renderer.js +++ b/addons/web/static/src/js/views/list/list_editable_renderer.js @@ -666,6 +666,17 @@ ListRenderer.include({ this._super.apply(this, arguments); } }, + /** + * Overrides to prevent from sorting if we are currently editing a record. + * + * @override + * @private + */ + _onSortColumn: function () { + if (this.currentRow === null) { + this._super.apply(this, arguments); + } + }, /** * Triggers a delete event. I don't know why we stop the propagation of the * event. diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js index 6f25cff4721..65b3b695894 100644 --- a/addons/web/static/tests/views/list_tests.js +++ b/addons/web/static/tests/views/list_tests.js @@ -436,6 +436,39 @@ QUnit.module('Views', { list.destroy(); }); + QUnit.test('editable list view: save data when list sorting in edit mode', function (assert) { + assert.expect(3); + + this.data.foo.fields.foo.sortable = true; + + var list = createView({ + View: ListView, + model: 'foo', + data: this.data, + arch: '', + mockRPC: function (route, args) { + if (args.method === 'write') { + assert.deepEqual(args.args, [[1], {foo: 'xyz'}], + "should correctly save the edited record"); + } + return this._super.apply(this, arguments); + } + }); + + list.$('.o_data_cell:first').click(); + list.$('input[name="foo"]').val('xyz').trigger('input'); + list.$('.o_column_sortable').click(); + + assert.ok(list.$('.o_data_row:first').hasClass('o_selected_row'), + "first row should still be in edition"); + + list.$buttons.find('.o_list_button_save').click(); + assert.ok(!list.$buttons.hasClass('o-editing'), + "list buttons should be back to their readonly mode"); + + list.destroy(); + }); + QUnit.test('selection changes are triggered correctly', function (assert) { assert.expect(8); @@ -861,6 +894,7 @@ QUnit.module('Views', { assert.ok(form.$('tbody tr:eq(2) td input').val(), "Value 2 should be third (shouldn't be sorted)"); + form.$('.o_form_sheet_bg').click(); // validate the row before sorting $o2m.find('.o_column_sortable').click(); assert.ok(form.$('tbody tr:first td:contains(Value 3)').length, "Value 3 should be first"); From dfe96ff38eff06ecc649c3f779e5163c34266c22 Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Tue, 19 Sep 2017 12:09:05 +0200 Subject: [PATCH 15/22] [FIX] stock: UoM on return lot - Create a product with: Product UoM: kg Purchase UoM: lb(s) Tracking: By lot(s) - Purchase 100 lb(s), validate and receive picking (set lot 001) - Return the picking - Set the lot The quantity to do is 45.36 instead of 100. We make sure to convert the quant UoM (=Product UoM) into the appropriate UoM. opw-771930 --- addons/stock/models/stock_picking.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/addons/stock/models/stock_picking.py b/addons/stock/models/stock_picking.py index 65aa42b69a5..e72ab2acc73 100644 --- a/addons/stock/models/stock_picking.py +++ b/addons/stock/models/stock_picking.py @@ -572,8 +572,11 @@ class Picking(models.Model): 'location_dest_id': mapping.location_dst_id, 'product_uom_id': uom.id, 'pack_lot_ids': [ - (0, 0, {'lot_id': lot, 'qty': 0.0, 'qty_todo': lots_grouped[mapping][lot]}) - for lot in lots_grouped.get(mapping, {}).keys()], + (0, 0, { + 'lot_id': lot, + 'qty': 0.0, + 'qty_todo': mapping.product.uom_id._compute_quantity(lots_grouped[mapping][lot], uom) + }) for lot in lots_grouped.get(mapping, {}).keys()], } product_id_to_vals.setdefault(mapping.product.id, list()).append(val_dict) From a5ca63c83beafd9a060ac58d3b4c6a9a987b28df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Wed, 20 Sep 2017 12:19:40 +0200 Subject: [PATCH 16/22] [FIX] sale: add force_save to make sure SO is correct Before the new views (SAAS15 and before), the 'readonly' modifier was kind of weird: a field which was in a readonly state was not saved (the field was not present in the RPC), UNLESS it is a subfield (for a x2many). With the new views, we changed the semantics, to make it more consistent: fields in readonly state are never sent to the server. This is a much simpler semantic, but it may introduce some problems, when we actually want to save the record. This is solved with a new attribute 'force_save', where we explicitely have to declare which fields should be save, even if they are in readonly mode. In this commit, we simply add the missing force_save attribute to the order line form view. This allow the user to create a new SO, add an order line with some invoice line, and save the result (without this, the server complains about a missing product_id field) --- addons/sale/views/sale_views.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/sale/views/sale_views.xml b/addons/sale/views/sale_views.xml index 5c30cb820f0..69a5b997651 100644 --- a/addons/sale/views/sale_views.xml +++ b/addons/sale/views/sale_views.xml @@ -207,6 +207,7 @@ From a0fddebe64eaaec9ea362ea2eabdaad7de61e724 Mon Sep 17 00:00:00 2001 From: Nicolas Lempereur Date: Fri, 15 Sep 2017 13:43:21 +0200 Subject: [PATCH 17/22] [FIX] base: prevent bin_size/binary visual defect Changing the address type of a contact in the "Contact & Addresses" tab could in some instance lead to a dead image (just visually, saving it would not change it) because the image is set to something like "data:image/png;base64,5.23 Kb" because of a combination of kanba/form x2many view, onchange, the differences of field present. In 9.0 this did not happen because the image was not a field in the form view corresponding to a contact of the kanban contacts view. In saa-15 thanks to bf26445 the image_small is different than image in the kanban view and this got us out of the issue. In saas-16 it is solved even further because new views only load missing fields. This commit just get out of the issue by preventing an update of the records field (with bin_size which caused the issue of the image) when opening the modal form view of a contact's contact. Ideally we should solve this more deeply but this is dangerous code to change (eg. removing the bin_size would solve it but this was done for a reason and could affect performances) and the effect is just visual in a very particular set of factors (if we save the form after the image is dead, the previous image reappeared). opw-765952 closes #19491 --- odoo/addons/base/res/res_partner_view.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/odoo/addons/base/res/res_partner_view.xml b/odoo/addons/base/res/res_partner_view.xml index 92d4c41db55..216063b6145 100644 --- a/odoo/addons/base/res/res_partner_view.xml +++ b/odoo/addons/base/res/res_partner_view.xml @@ -219,6 +219,11 @@ + + + + + From 776e77b67c2abea9cef8ab7071998d4272c3404f Mon Sep 17 00:00:00 2001 From: Goffin Simon Date: Wed, 20 Sep 2017 14:16:12 +0200 Subject: [PATCH 18/22] [FIX] account, point_of_sale, purchase, sale, website_sale: correctly set included tax in multi-company A product can have multiple included taxes (i.e. one by company). When computing the product price with function _fix_tax_included_price, only the taxes of the company should be considered. Otherwise it could remove all the included taxes of all companies. ps: When no company is set on the SO, all the taxes visible from the company of the user are set for the product of the line. Same behavior as in _compute_tax_id. Closes #19566 opw:770464 --- addons/account/models/account.py | 8 ++++++++ addons/point_of_sale/models/pos_order.py | 2 +- addons/purchase/models/purchase.py | 8 ++++---- addons/sale/models/sale.py | 6 +++--- addons/website_sale/models/sale_order.py | 7 ++++--- 5 files changed, 20 insertions(+), 11 deletions(-) diff --git a/addons/account/models/account.py b/addons/account/models/account.py index 6a2e73160bd..5a7ad534da7 100644 --- a/addons/account/models/account.py +++ b/addons/account/models/account.py @@ -830,6 +830,14 @@ class AccountTax(models.Model): return incl_tax.compute_all(price)['total_excluded'] return price + @api.model + def _fix_tax_included_price_company(self, price, prod_taxes, line_taxes, company_id): + if company_id: + #To keep the same behavior as in _compute_tax_id + prod_taxes = prod_taxes.filtered(lambda tax: tax.company_id == company_id) + line_taxes = line_taxes.filtered(lambda tax: tax.company_id == company_id) + return self._fix_tax_included_price(price, prod_taxes, line_taxes) + class AccountReconcileModel(models.Model): _name = "account.reconcile.model" _description = "Preset to create journal entries during a invoices and payments matching" diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py index 1f7e0762e65..840ff6869b9 100644 --- a/addons/point_of_sale/models/pos_order.py +++ b/addons/point_of_sale/models/pos_order.py @@ -869,7 +869,7 @@ class PosOrderLine(models.Model): self.tax_ids = self.product_id.taxes_id.filtered(lambda r: not self.company_id or r.company_id == self.company_id) fpos = self.order_id.fiscal_position_id tax_ids_after_fiscal_position = fpos.map_tax(self.tax_ids, self.product_id, self.order_id.partner_id) if fpos else self.tax_ids - self.price_unit = self.env['account.tax']._fix_tax_included_price(price, self.product_id.taxes_id, tax_ids_after_fiscal_position) + self.price_unit = self.env['account.tax']._fix_tax_included_price_company(price, self.product_id.taxes_id, tax_ids_after_fiscal_position, self.company_id) @api.onchange('qty', 'discount', 'price_unit', 'tax_ids') def _onchange_qty(self): diff --git a/addons/purchase/models/purchase.py b/addons/purchase/models/purchase.py index 7cd1132d89e..419d184f24e 100644 --- a/addons/purchase/models/purchase.py +++ b/addons/purchase/models/purchase.py @@ -822,7 +822,7 @@ class PurchaseOrderLine(models.Model): if not seller: return - price_unit = self.env['account.tax']._fix_tax_included_price(seller.price, self.product_id.supplier_taxes_id, self.taxes_id) if seller else 0.0 + price_unit = self.env['account.tax']._fix_tax_included_price_company(seller.price, self.product_id.supplier_taxes_id, self.taxes_id, self.company_id) if seller else 0.0 if price_unit and seller and self.order_id.currency_id and seller.currency_id != self.order_id.currency_id: price_unit = seller.currency_id.compute(price_unit, self.order_id.currency_id) @@ -895,7 +895,7 @@ class ProcurementOrder(models.Model): date=procurement.purchase_line_id.order_id.date_order and procurement.purchase_line_id.order_id.date_order[:10], uom_id=procurement.purchase_line_id.product_uom) - price_unit = self.env['account.tax']._fix_tax_included_price(seller.price, procurement.purchase_line_id.product_id.supplier_taxes_id, procurement.purchase_line_id.taxes_id) if seller else 0.0 + price_unit = self.env['account.tax']._fix_tax_included_price_company(seller.price, procurement.purchase_line_id.product_id.supplier_taxes_id, procurement.purchase_line_id.taxes_id, self.company_id) if seller else 0.0 if price_unit and seller and procurement.purchase_line_id.order_id.currency_id and seller.currency_id != procurement.purchase_line_id.order_id.currency_id: price_unit = seller.currency_id.compute(price_unit, procurement.purchase_line_id.order_id.currency_id) @@ -954,7 +954,7 @@ class ProcurementOrder(models.Model): if taxes_id: taxes_id = taxes_id.filtered(lambda x: x.company_id.id == self.company_id.id) - price_unit = self.env['account.tax']._fix_tax_included_price(seller.price, self.product_id.supplier_taxes_id, taxes_id) if seller else 0.0 + price_unit = self.env['account.tax']._fix_tax_included_price_company(seller.price, self.product_id.supplier_taxes_id, taxes_id, self.company_id) if seller else 0.0 if price_unit and seller and po.currency_id and seller.currency_id != po.currency_id: price_unit = seller.currency_id.compute(price_unit, po.currency_id) @@ -1079,7 +1079,7 @@ class ProcurementOrder(models.Model): date=po.date_order and po.date_order[:10], uom_id=procurement.product_id.uom_po_id) - price_unit = self.env['account.tax']._fix_tax_included_price(seller.price, line.product_id.supplier_taxes_id, line.taxes_id) if seller else 0.0 + price_unit = self.env['account.tax']._fix_tax_included_price_company(seller.price, line.product_id.supplier_taxes_id, line.taxes_id, self.company_id) if seller else 0.0 if price_unit and seller and po.currency_id and seller.currency_id != po.currency_id: price_unit = seller.currency_id.compute(price_unit, po.currency_id) diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py index 38a4036f609..50d2066fc14 100644 --- a/addons/sale/models/sale.py +++ b/addons/sale/models/sale.py @@ -903,7 +903,7 @@ class SaleOrderLine(models.Model): self._compute_tax_id() if self.order_id.pricelist_id and self.order_id.partner_id: - vals['price_unit'] = self.env['account.tax']._fix_tax_included_price(self._get_display_price(product), product.taxes_id, self.tax_id) + vals['price_unit'] = self.env['account.tax']._fix_tax_included_price_company(self._get_display_price(product), product.taxes_id, self.tax_id, self.company_id) self.update(vals) return result @@ -923,7 +923,7 @@ class SaleOrderLine(models.Model): uom=self.product_uom.id, fiscal_position=self.env.context.get('fiscal_position') ) - self.price_unit = self.env['account.tax']._fix_tax_included_price(self._get_display_price(product), product.taxes_id, self.tax_id) + self.price_unit = self.env['account.tax']._fix_tax_included_price_company(self._get_display_price(product), product.taxes_id, self.tax_id, self.company_id) @api.multi def unlink(self): @@ -999,7 +999,7 @@ class SaleOrderLine(models.Model): 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) - new_list_price = self.env['account.tax']._fix_tax_included_price(new_list_price, self.product_id.taxes_id, self.tax_id) + new_list_price = self.env['account.tax']._fix_tax_included_price_company(new_list_price, self.product_id.taxes_id, self.tax_id, self.company_id) if new_list_price != 0: if self.product_id.company_id and self.order_id.pricelist_id.currency_id != self.product_id.company_id.currency_id: diff --git a/addons/website_sale/models/sale_order.py b/addons/website_sale/models/sale_order.py index 9d1564e06cd..6735d3848f9 100644 --- a/addons/website_sale/models/sale_order.py +++ b/addons/website_sale/models/sale_order.py @@ -71,7 +71,7 @@ class SaleOrder(models.Model): if order.pricelist_id and order.partner_id: order_line = order._cart_find_product_line(product.id) if order_line: - pu = self.env['account.tax']._fix_tax_included_price(pu, product.taxes_id, order_line[0].tax_id) + pu = self.env['account.tax']._fix_tax_included_price_company(pu, product.taxes_id, order_line[0].tax_id, self.company_id) return { 'product_id': product_id, @@ -159,10 +159,11 @@ class SaleOrder(models.Model): 'pricelist': order.pricelist_id.id, }) product = self.env['product.product'].with_context(product_context).browse(product_id) - values['price_unit'] = self.env['account.tax']._fix_tax_included_price( + values['price_unit'] = self.env['account.tax']._fix_tax_included_price_company( order_line._get_display_price(product), order_line.product_id.taxes_id, - order_line.tax_id + order_line.tax_id, + self.company_id ) order_line.write(values) From 130c47bc0abee1ab193fb136bd1844dba8a693cd Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Wed, 20 Sep 2017 14:45:23 +0200 Subject: [PATCH 19/22] [FIX] web: several minor issues with editable list - correctly update the pager when a record is added or removed - destroy the widgets of the removed row when the user clicks on 'Cancel' when creating a new record - ensure that the list always contains 4 rows (before this rev., in list views with less than 4 records, if the user created a new record, then clicked on 'Discard', there were only 3 rows left - properly destroy field widgets when removed from the DOM Note that the change of parent.parentID to parent.static is not a bug fix, but it's better to use the key that indicates the type of list element, instead of implying it from other attributes. --- .../static/src/js/views/basic/basic_model.js | 3 +- .../src/js/views/basic/basic_renderer.js | 18 +++---- .../src/js/views/list/list_controller.js | 2 + .../js/views/list/list_editable_renderer.js | 21 ++++++-- addons/web/static/tests/views/list_tests.js | 49 +++++++++++++++++++ 5 files changed, 79 insertions(+), 14 deletions(-) diff --git a/addons/web/static/src/js/views/basic/basic_model.js b/addons/web/static/src/js/views/basic/basic_model.js index cf5a18488a6..103c0d4c5d2 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -768,7 +768,7 @@ var BasicModel = AbstractModel.extend({ removeLine: function (elementID) { var record = this.localData[elementID]; var parent = this.localData[record.parentID]; - if (parent.parentID) { + if (parent.static) { // x2Many case: the new record has been stored in _changes, as a // command so we remove the command(s) related to that record parent._changes = _.filter(parent._changes, function (change) { @@ -777,6 +777,7 @@ var BasicModel = AbstractModel.extend({ } else { // main list view case: the new record is in data parent.data = _.without(parent.data, elementID); + parent.count--; } }, /** diff --git a/addons/web/static/src/js/views/basic/basic_renderer.js b/addons/web/static/src/js/views/basic/basic_renderer.js index e040a5fa7f0..09b264be0b5 100644 --- a/addons/web/static/src/js/views/basic/basic_renderer.js +++ b/addons/web/static/src/js/views/basic/basic_renderer.js @@ -273,18 +273,18 @@ var BasicRenderer = AbstractRenderer.extend({ * from internal referencing. * * @private - * @param {Object} record + * @param {string} recordID id of the local resource * @param {AbstractField} widget * @returns {integer} the index of the removed widget */ - _destroyFieldWidget: function (record, widget) { - var recordWidgets = this.allFieldWidgets[record.id]; + _destroyFieldWidget: function (recordID, widget) { + var recordWidgets = this.allFieldWidgets[recordID]; var index = recordWidgets.indexOf(widget); if (index >= 0) { recordWidgets.splice(index, 1); } - this._unregisterModifiersElement(widget.__node, record, widget); - widget.$el.destroy(); + this._unregisterModifiersElement(widget.__node, recordID, widget); + widget.destroy(); return index; }, /** @@ -541,7 +541,7 @@ var BasicRenderer = AbstractRenderer.extend({ widget.$el.replaceWith(newWidget.$el); // Destroy the old widget and position the new one at the old one's - var oldIndex = this._destroyFieldWidget(record, widget); + var oldIndex = this._destroyFieldWidget(record.id, widget); var recordWidgets = this.allFieldWidgets[record.id]; recordWidgets.splice(oldIndex, 0, newWidget); recordWidgets.pop(); @@ -553,13 +553,13 @@ var BasicRenderer = AbstractRenderer.extend({ * node and record. * * @param {Object} node - * @param {Object} record + * @param {string} recordID id of the local resource * @param {jQuery|AbstractField} element */ - _unregisterModifiersElement: function (node, record, element) { + _unregisterModifiersElement: function (node, recordID, element) { var modifiersData = this._getModifiersData(node); if (modifiersData) { - var elements = modifiersData.elementsByRecord[record.id]; + var elements = modifiersData.elementsByRecord[recordID]; var index = _.findIndex(elements, function (oldElement) { return oldElement.widget === element || oldElement.$el[0] === element[0]; diff --git a/addons/web/static/src/js/views/list/list_controller.js b/addons/web/static/src/js/views/list/list_controller.js index 7222d792c69..460ad5de100 100644 --- a/addons/web/static/src/js/views/list/list_controller.js +++ b/addons/web/static/src/js/views/list/list_controller.js @@ -182,6 +182,7 @@ var ListController = BasicController.extend({ if ((recordID || this.handle) !== this.handle) { var state = this.model.get(this.handle); this.renderer.removeLine(state, recordID); + this._updatePager(); } }, /** @@ -202,6 +203,7 @@ var ListController = BasicController.extend({ var state = self.model.get(self.handle); self.renderer.updateState(state, {}); self.renderer.editRecord(recordID); + self._updatePager(); }).always(this._enableButtons.bind(this)); }, /** diff --git a/addons/web/static/src/js/views/list/list_editable_renderer.js b/addons/web/static/src/js/views/list/list_editable_renderer.js index b101405e957..6519fdfaec4 100644 --- a/addons/web/static/src/js/views/list/list_editable_renderer.js +++ b/addons/web/static/src/js/views/list/list_editable_renderer.js @@ -133,6 +133,7 @@ ListRenderer.include({ * @param {string} recordID */ removeLine: function (state, recordID) { + var self = this; var rowIndex = _.findIndex(this.state.data, {id: recordID}); this.state = state; if (rowIndex === -1) { @@ -141,8 +142,20 @@ ListRenderer.include({ if (rowIndex === this.currentRow) { this.currentRow = null; } + + // remove the row var $row = this.$('.o_data_row:nth(' + rowIndex + ')'); - $row.remove(); + if (this.state.count >= 4) { + $row.remove(); + } else { + $row.replaceWith(this._renderEmptyRow()); + } + + // destroy the removed row's widgets + var widgetsToDestroy = this.allFieldWidgets[recordID].slice(); + _.each(widgetsToDestroy, function (widget) { + self._destroyFieldWidget(recordID, widget); + }); }, /** * Updates the already rendered row associated to the given recordID so that @@ -221,7 +234,7 @@ ListRenderer.include({ // destroyed. This is not the case for simple buttons so we have to // do it here. if ($td.hasClass('o_list_button')) { - self._unregisterModifiersElement(node, record, $td.children()); + self._unregisterModifiersElement(node, recordID, $td.children()); } // For edit mode we only replace the content of the cell with its @@ -231,14 +244,14 @@ ListRenderer.include({ if (editMode) { $td.empty().append($newTd.contents()); } else { - self._unregisterModifiersElement(node, record, $td); + self._unregisterModifiersElement(node, recordID, $td); $td.replaceWith($newTd); } }); delete this.defs; // Destroy old field widgets - _.each(oldWidgets, this._destroyFieldWidget.bind(this, record)); + _.each(oldWidgets, this._destroyFieldWidget.bind(this, recordID)); // Toggle selected class here so that style is applied at the end $row.toggleClass('o_selected_row', editMode); diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js index 65b3b695894..a7368c75325 100644 --- a/addons/web/static/tests/views/list_tests.js +++ b/addons/web/static/tests/views/list_tests.js @@ -2,6 +2,7 @@ odoo.define('web.list_tests', function (require) { "use strict"; var config = require('web.config'); +var basicFields = require('web.basic_fields'); var FormView = require('web.FormView'); var ListView = require('web.ListView'); var testUtils = require('web.test_utils'); @@ -388,6 +389,54 @@ QUnit.module('Views', { list.destroy(); }); + QUnit.test('editable list: add a line and discard', function (assert) { + assert.expect(11); + + var oldDestroy = basicFields.FieldChar.prototype.destroy; + basicFields.FieldChar.prototype.destroy = function () { + assert.step('destroy'); + oldDestroy.apply(this, arguments); + }; + + var list = createView({ + View: ListView, + model: 'foo', + data: this.data, + arch: '', + domain: [['foo', '=', 'yop']], + }); + + assert.strictEqual(list.$('tbody tr').length, 4, + "list should contain 4 rows"); + assert.strictEqual(list.$('.o_data_row').length, 1, + "list should contain one record (and thus 3 empty rows)"); + assert.strictEqual(list.pager.$('.o_pager_value').text(), '1-1', + "pager should be correct"); + + list.$buttons.find('.o_list_button_add').click(); + + assert.strictEqual(list.$('tbody tr').length, 4, + "list should still contain 4 rows"); + assert.strictEqual(list.$('.o_data_row').length, 2, + "list should contain two record (and thus 2 empty rows)"); + assert.strictEqual(list.pager.$('.o_pager_value').text(), '1-2', + "pager should be correct"); + + list.$buttons.find('.o_list_button_discard').click(); + + assert.strictEqual(list.$('tbody tr').length, 4, + "list should still contain 4 rows"); + assert.strictEqual(list.$('.o_data_row').length, 1, + "list should contain one record (and thus 3 empty rows)"); + assert.strictEqual(list.pager.$('.o_pager_value').text(), '1-1', + "pager should be correct"); + assert.verifySteps(['destroy'], + "should have destroyed the widget of the removed line"); + + basicFields.FieldChar.prototype.destroy = oldDestroy; + list.destroy(); + }); + QUnit.test('field changes are triggered correctly', function (assert) { assert.expect(2); From c3a32fc98c0a0c485798c76f0496b192ba8afa54 Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Wed, 20 Sep 2017 13:21:23 +0200 Subject: [PATCH 20/22] [FIX] web: keep settings form view in edit mode when coming back using the breadcrumbs. The settings form view should always be displayed in 'edit' mode. Before this rev., it was re-rendered in 'readonly' when the user clicked on a link in a setting view (opening another view stacked in the breadcrumbs), and then came back to the settings view using the breadcrumbs. For instance, go to 'General Settings', click on 'Activity Types', come back. --- addons/web/static/src/js/chrome/view_manager.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/addons/web/static/src/js/chrome/view_manager.js b/addons/web/static/src/js/chrome/view_manager.js index c76c7aecc4a..4548721690d 100644 --- a/addons/web/static/src/js/chrome/view_manager.js +++ b/addons/web/static/src/js/chrome/view_manager.js @@ -292,12 +292,7 @@ var ViewManager = Widget.extend(ControlPanelMixin, { }).fail(view.loaded.reject.bind(view.loaded)); } else { view.loaded = view.loaded.then(function() { - // By default, the view will be loaded in readonly mode - // Returns to this default mode if you load action from breadcrumb view_options = _.extend({}, view_options, self.env); - if (view_type === 'form') { - view_options.mode = view_options.mode || 'readonly'; - } return view.controller.reload(view_options); }); } @@ -382,8 +377,14 @@ var ViewManager = Widget.extend(ControlPanelMixin, { }); }, select_view: function (index) { - var view_type = this.view_stack[index].type; - return this.switch_mode(view_type); + var viewType = this.view_stack[index].type; + var viewOptions = {}; + if (viewType === 'form') { + // reload form views in readonly, except for inline actions (i.e. + // settings views) that stay in edit + viewOptions.mode = this.action.target === 'inline' ? 'edit' : 'readonly'; + } + return this.switch_mode(viewType, viewOptions); }, /** * Renders the switch buttons for multi- and mono-record views and adds From 5b216c03c1c65f8c0ff651d21bad4177926c7621 Mon Sep 17 00:00:00 2001 From: Haresh Shyara Date: Tue, 1 Aug 2017 18:39:53 +0530 Subject: [PATCH 21/22] [FIX] account: wrong default journal on vendor credit note --- addons/account/views/account_invoice_view.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/account/views/account_invoice_view.xml b/addons/account/views/account_invoice_view.xml index b2d4f2c0fd4..528c57c2dbc 100644 --- a/addons/account/views/account_invoice_view.xml +++ b/addons/account/views/account_invoice_view.xml @@ -652,7 +652,7 @@ tree,kanban,form,calendar,pivot,graph [('type','=','out_refund')] - {'default_type': 'out_refund', 'journal_type': 'sale'} + {'default_type': 'out_refund', 'type': 'out_refund', 'journal_type': 'sale'}

@@ -726,7 +726,7 @@ tree,kanban,form,calendar,pivot,graph [('type','=','in_refund')] - {'default_type': 'in_refund', 'journal_type': 'purchase'} + {'default_type': 'in_refund', 'type': 'in_refund', 'journal_type': 'purchase'}

From b51b0d66c2f71593d5eb3f153d55b1b97d979da7 Mon Sep 17 00:00:00 2001 From: qsm-odoo Date: Thu, 21 Sep 2017 12:22:43 +0200 Subject: [PATCH 22/22] [FIX] web: open domain selector's datepicker at the right position Before this commit, and since the combination of new views + datepicker library update, the datepicker opened at the top left of the window when using it in a domain selector. It also could not be used at all. This was because the current scenario occured: 1) Click on the datepicker 2) The datepicker triggers that it has changed on opening 3) The whole domain selector is rerendered 4) The datepicker computes where it should open... on basis of the old destroyed domain selector As the main cause of the problem here was the (2), this commit changes the datepicker odoo widget to only notifies that it has changed when it has really changed. --- addons/web/static/src/js/widgets/date_picker.js | 11 +++++++++-- .../static/tests/widgets/domain_selector_tests.js | 12 ++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/addons/web/static/src/js/widgets/date_picker.js b/addons/web/static/src/js/widgets/date_picker.js index 76281d9366a..b95a1c4f936 100644 --- a/addons/web/static/src/js/widgets/date_picker.js +++ b/addons/web/static/src/js/widgets/date_picker.js @@ -74,9 +74,16 @@ var DateWidget = Widget.extend({ * set datetime value */ changeDatetime: function () { - if(this.isValid()) { + if (this.isValid()) { + var oldValue = this.getValue(); this._setValueFromUi(); - this.trigger("datetime_changed"); + var newValue = this.getValue(); + + if (!oldValue !== !newValue || oldValue && newValue && !oldValue.isSame(newValue)) { + // The condition is strangely written; this is because the + // values can be false/undefined + this.trigger("datetime_changed"); + } } }, /** diff --git a/addons/web/static/tests/widgets/domain_selector_tests.js b/addons/web/static/tests/widgets/domain_selector_tests.js index 19731156a2c..77d8eeb24da 100644 --- a/addons/web/static/tests/widgets/domain_selector_tests.js +++ b/addons/web/static/tests/widgets/domain_selector_tests.js @@ -151,7 +151,7 @@ QUnit.module('DomainSelector', { }); QUnit.test("building a domain with a datetime", function (assert) { - assert.expect(1); + assert.expect(2); var $target = $("#qunit-fixture"); @@ -163,8 +163,16 @@ QUnit.module('DomainSelector', { domainSelector.appendTo($target); // Check that there is a datepicker to choose the date - assert.strictEqual(domainSelector.$(".o_datepicker:visible").length, 1, + var $datepicker = domainSelector.$(".o_datepicker:visible"); + assert.strictEqual($datepicker.length, 1, "there should be a datepicker"); + + var val = $datepicker.find('input').focus().click().val(); + $('.bootstrap-datetimepicker-widget :not(.today)[data-action="selectDay"]').click(); + assert.notEqual(domainSelector.$(".o_datepicker:visible input").val(), val, + "datepicker value should have changed"); + + domainSelector.destroy(); }); }); });