diff options
70 files changed, 531 insertions, 80 deletions
diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index f343c161d2f..172be8b3c84 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -1912,7 +1912,10 @@ class AccountInvoiceLine(models.Model): return if not self.product_id: fpos = self.invoice_id.fiscal_position_id - default_tax = self.invoice_id.type in ('out_invoice', 'out_refund') and self.invoice_id.company_id.account_sale_tax_id or self.invoice_id.company_id.account_purchase_tax_id + if self.invoice_id.type in ('out_invoice', 'out_refund'): + default_tax = self.invoice_id.company_id.account_sale_tax_id + else: + default_tax = self.invoice_id.company_id.account_purchase_tax_id self.invoice_line_tax_ids = fpos.map_tax(self.account_id.tax_ids or default_tax, partner=self.partner_id).ids elif not self.price_unit: self._set_taxes() diff --git a/addons/account/report/account_invoice_report.py b/addons/account/report/account_invoice_report.py index 614aba000ab..f6e312b2110 100644 --- a/addons/account/report/account_invoice_report.py +++ b/addons/account/report/account_invoice_report.py @@ -189,10 +189,9 @@ class ReportInvoiceWithPayment(models.AbstractModel): @api.model def _get_report_values(self, docids, data=None): - report = self.env['ir.actions.report']._get_report_from_name('account.report_invoice_with_payments') return { 'doc_ids': docids, - 'doc_model': report.model, - 'docs': self.env[report.model].browse(docids), + 'doc_model': 'account.invoice', + 'docs': self.env['account.invoice'].browse(docids), 'report_type': data.get('report_type') if data else '', } diff --git a/addons/account/static/src/js/reconciliation/reconciliation_model.js b/addons/account/static/src/js/reconciliation/reconciliation_model.js index 2b29ed7e87a..a36740e7d2f 100644 --- a/addons/account/static/src/js/reconciliation/reconciliation_model.js +++ b/addons/account/static/src/js/reconciliation/reconciliation_model.js @@ -909,7 +909,7 @@ var StatementModel = BasicModel.extend({ 'link': prop.id, 'tax_id': [tax.id, null], 'amount': tax.amount, - 'label': tax.name, + 'label': prop.label ? prop.label + " " + tax.name : tax.name, 'date': prop.date, 'account_id': tax.account_id ? [tax.account_id, null] : prop.account_id, 'analytic': tax.analytic, diff --git a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js index 33e5844fc75..b6540bd0c97 100644 --- a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js +++ b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js @@ -774,6 +774,7 @@ var LineRenderer = Widget.extend(FieldManagerMixin, { */ _onSelectMoveLine: function (event) { var $el = $(event.target); + $el.prop('disabled', true); this._destroyPopover($el); var moveLineId = $el.closest('.mv_line').data('line-id'); this.trigger_up('add_proposition', {'data': moveLineId}); diff --git a/addons/account/static/tests/reconciliation_tests.js b/addons/account/static/tests/reconciliation_tests.js index a1f9c92cd6d..aa46417b170 100644 --- a/addons/account/static/tests/reconciliation_tests.js +++ b/addons/account/static/tests/reconciliation_tests.js @@ -1273,14 +1273,14 @@ QUnit.module('account', { widget.$('.create .quick_add button:contains(ATOS)').trigger('click'); assert.strictEqual(widget.$('.accounting_view tbody .cell_label, .accounting_view tbody .cell_right').text().replace(/[\n\r\s$,]+/g, ' '), - " ATOS Banque 1145.62 Tax 20.00% 229.12 ATOS Frais 26.71 Tax 10.00% include 2.67 ", "should display 4 lines"); + " ATOS Banque 1145.62 ATOS Banque Tax 20.00% 229.12 ATOS Frais 26.71 ATOS Frais Tax 10.00% include 2.67 ", "should display 4 lines"); assert.strictEqual(widget.$('.accounting_view tfoot .cell_label, .accounting_view tfoot .cell_left').text().replace(/[\n\r\s$,]+/g, ' '), "Open balance229.12", "should display the 'Open balance' line with value in left column"); testUtils.fields.editInput(widget.$('.create .create_amount input'), '100'); assert.strictEqual(widget.$('.accounting_view tbody').text().replace(/[\n\r\s$,]+/g, ' '), - " 101120 New ATOS Banque 1075.00 101120 New Tax 20.00% 215.00 101130 New ATOS Frais 90.91 101300 New Tax 10.00% include 9.09 ", + " 101120 New ATOS Banque 1075.00 101120 New ATOS Banque Tax 20.00% 215.00 101130 New ATOS Frais 90.91 101300 New ATOS Frais Tax 10.00% include 9.09 ", "should update the value of the 4 lines (because the line must have 100% of the value)"); assert.strictEqual(widget.$('.accounting_view tfoot .cell_label, .accounting_view tfoot .cell_left').text().replace(/[\n\r\s$,]+/g, ' '), "Open balance215.00", "should change the 'Open balance' line because the 20.00% tax is not an include tax"); diff --git a/addons/account/wizard/account_invoice_send_views.xml b/addons/account/wizard/account_invoice_send_views.xml index eac9adda58e..cae3b296b63 100644 --- a/addons/account/wizard/account_invoice_send_views.xml +++ b/addons/account/wizard/account_invoice_send_views.xml @@ -14,7 +14,6 @@ <field name="invoice_ids" invisible="1"/> <field name="email_from" invisible="1" /> <field name="mail_server_id" invisible="1"/> - <field name="reply_to" invisible="1"/> <div name="option_print"> <field name="is_print" /> <b><label for="is_print"/></b> diff --git a/addons/account_analytic_default/models/account_analytic_default.py b/addons/account_analytic_default/models/account_analytic_default.py index 34052a51560..707e9920ac0 100644 --- a/addons/account_analytic_default/models/account_analytic_default.py +++ b/addons/account_analytic_default/models/account_analytic_default.py @@ -73,8 +73,9 @@ class AccountInvoiceLine(models.Model): fields.Date.today(), company_id=self.company_id.id ) - self.account_analytic_id = rec.analytic_id.id - self.analytic_tag_ids = rec.analytic_tag_ids.ids + if rec: + self.account_analytic_id = rec.analytic_id.id + self.analytic_tag_ids = rec.analytic_tag_ids.ids return res def _set_additional_fields(self, invoice): diff --git a/addons/account_voucher/models/account_voucher.py b/addons/account_voucher/models/account_voucher.py index 3cb54bf3b43..a34367ddedd 100644 --- a/addons/account_voucher/models/account_voucher.py +++ b/addons/account_voucher/models/account_voucher.py @@ -347,10 +347,13 @@ class AccountVoucher(models.Model): } if company_currency != current_currency: ctx = {} + sign = temp['credit'] and -1 or 1 + amount_currency = company_cur._convert(tax_vals['amount'], current_cur, line.company_id, + self.account_date or fields.Date.today(), round=True) if self.account_date: ctx['date'] = self.account_date temp['currency_id'] = current_currency - temp['amount_currency'] = company_cur._convert(tax_vals['amount'], current_cur, line.company_id, self.account_date or fields.Date.today(), round=True) + temp['amount_currency'] = sign * abs(amount_currency) self.env['account.move.line'].create(temp) # When global rounding is activated, we must wait until all tax lines are computed to diff --git a/addons/crm/views/crm_lead_views.xml b/addons/crm/views/crm_lead_views.xml index 4f2fdc9606c..9da4cd19c21 100644 --- a/addons/crm/views/crm_lead_views.xml +++ b/addons/crm/views/crm_lead_views.xml @@ -92,7 +92,7 @@ <field name="zip" placeholder="ZIP" class="o_address_zip"/> <field name="country_id" placeholder="Country" class="o_address_country" options='{"no_open": True, "no_create": True}'/> </div> - <field name="website" widget="url" placeholder="e.g. www.odoo.com"/> + <field name="website" widget="url" placeholder="e.g. https://www.odoo.com"/> </group> <group> <label for="contact_name"/> @@ -603,7 +603,7 @@ <field name="zip" placeholder="ZIP" class="o_address_zip"/> <field name="country_id" placeholder="Country" class="o_address_country" options='{"no_open": True, "no_create": True}'/> </div> - <field name="website" widget="url" placeholder="e.g. www.odoo.com"/> + <field name="website" widget="url" placeholder="e.g. https://www.odoo.com"/> </group> <group class="mt48"> diff --git a/addons/digest/views/digest_templates.xml b/addons/digest/views/digest_templates.xml index 0010ad6a1c0..e7cd42d5df5 100644 --- a/addons/digest/views/digest_templates.xml +++ b/addons/digest/views/digest_templates.xml @@ -6,7 +6,7 @@ <div class="col-lg-6 offset-lg-3"> <h3>Digest Subscriptions</h3> <div class="alert alert-success text-center" role="status"> - <p>You have been successfully unsubscribed from <string t-field="digest.name"/></p> + <p>You have been successfully unsubscribed from <strong t-field="digest.name"/></p> </div> </div> </div> diff --git a/addons/google_drive/models/res_config_settings.py b/addons/google_drive/models/res_config_settings.py index 49d7ff7d86d..eff3d6c3f6f 100644 --- a/addons/google_drive/models/res_config_settings.py +++ b/addons/google_drive/models/res_config_settings.py @@ -28,10 +28,12 @@ class ResConfigSettings(models.TransientModel): params = self.env['ir.config_parameter'].sudo() authorization_code_before = params.get_param('google_drive_authorization_code') authorization_code = self.google_drive_authorization_code - refresh_token = False - if authorization_code and authorization_code != authorization_code_before: - refresh_token = self.env['google.service'].generate_refresh_token('drive', authorization_code) - params.set_param('google_drive_refresh_token', refresh_token) + if authorization_code != authorization_code_before: + refresh_token = ( + self.env['google.service'].generate_refresh_token('drive', authorization_code) + if authorization_code else False + ) + params.set_param('google_drive_refresh_token', refresh_token) @api.multi def action_setup_token(self): diff --git a/addons/hr/security/hr_security.xml b/addons/hr/security/hr_security.xml index aa08e5f0762..9dcd218f902 100644 --- a/addons/hr/security/hr_security.xml +++ b/addons/hr/security/hr_security.xml @@ -42,5 +42,12 @@ <field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field> </record> + <record id="hr_employee_comp_rule" model="ir.rule"> + <field name="name">Employee multi company rule</field> + <field name="model_id" ref="model_hr_employee"/> + <field name="global" eval="True"/> + <field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field> + </record> + </data> </odoo> diff --git a/addons/hr_holidays/models/hr_leave.py b/addons/hr_holidays/models/hr_leave.py index ce41ff33e7e..8474255aaa6 100644 --- a/addons/hr_holidays/models/hr_leave.py +++ b/addons/hr_holidays/models/hr_leave.py @@ -554,6 +554,8 @@ class HolidaysRequest(models.Model): if not values.get('department_id'): values.update({'department_id': self.env['hr.employee'].browse(employee_id).department_id.id}) holiday = super(HolidaysRequest, self.with_context(mail_create_nolog=True, mail_create_nosubscribe=True)).create(values) + if self._context.get('import_file'): + holiday._onchange_leave_dates() if not self._context.get('leave_fast_create'): holiday.add_follower(employee_id) if holiday.validation_type == 'hr': diff --git a/addons/hr_holidays/models/hr_leave_allocation.py b/addons/hr_holidays/models/hr_leave_allocation.py index ab93459622b..f21d16da010 100644 --- a/addons/hr_holidays/models/hr_leave_allocation.py +++ b/addons/hr_holidays/models/hr_leave_allocation.py @@ -367,7 +367,8 @@ class HolidaysAllocation(models.Model): holiday.message_subscribe(partner_ids=(holiday.employee_id.parent_id.user_id.partner_id | holiday.employee_id.leave_manager_id.partner_id).ids) if 'employee_id' in values: holiday._onchange_employee() - holiday.activity_update() + if not self._context.get('import_file'): + holiday.activity_update() return holiday @api.multi diff --git a/addons/hr_timesheet/views/res_config_settings_views.xml b/addons/hr_timesheet/views/res_config_settings_views.xml index 6588b0b2d14..2cb4437556a 100644 --- a/addons/hr_timesheet/views/res_config_settings_views.xml +++ b/addons/hr_timesheet/views/res_config_settings_views.xml @@ -105,5 +105,5 @@ </record> <menuitem id="hr_timesheet_config_settings_menu_action" name="Settings" parent="hr_timesheet_menu_configuration" - action="hr_timesheet_config_settings_action" sequence="0"/> + action="hr_timesheet_config_settings_action" sequence="0" groups="base.group_system"/> </odoo> diff --git a/addons/hr_timesheet_attendance/report/hr_timesheet_attendance_report.py b/addons/hr_timesheet_attendance/report/hr_timesheet_attendance_report.py index 572ba6d5ce9..166e6f23065 100644 --- a/addons/hr_timesheet_attendance/report/hr_timesheet_attendance_report.py +++ b/addons/hr_timesheet_attendance/report/hr_timesheet_attendance_report.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo import api, fields, models +from odoo import api, fields, models, tools class TimesheetAttendance(models.Model): @@ -17,7 +17,7 @@ class TimesheetAttendance(models.Model): @api.model_cr def init(self): - self._cr.execute("DROP VIEW IF EXISTS hr_timesheet_attendance_report") + tools.drop_view_if_exists(self.env.cr, self._table) self._cr.execute("""CREATE OR REPLACE VIEW %s AS ( SELECT max(id) AS id, diff --git a/addons/l10n_es/data/account.account.template-common.csv b/addons/l10n_es/data/account.account.template-common.csv index 21c952cf79c..4e5121d061e 100644 --- a/addons/l10n_es/data/account.account.template-common.csv +++ b/addons/l10n_es/data/account.account.template-common.csv @@ -276,7 +276,7 @@ "account_common_523","Proveedores de inmovilizado a corto plazo","523","account.data_account_type_payable","l10n_es.account_chart_template_common","l10n_es.account_group_523","True" "account_common_524","Acreedores por arrendamiento financiero a corto plazo","524","account.data_account_type_current_liabilities","l10n_es.account_chart_template_common","l10n_es.account_group_524","False" "account_common_525","Efectos a pagar a corto plazo","525","account.data_account_type_current_liabilities","l10n_es.account_chart_template_common","l10n_es.account_group_525","False" -"account_common_526","Dividendo activo a pagar","526","account.data_account_type_current_liabilities","l10n_es.account_chart_template_common","l10n_es.account_group_526","False" +"account_common_526","Dividendo activo a pagar","526","account.data_account_type_payable","l10n_es.account_chart_template_common","l10n_es.account_group_526","True" "account_common_527","Intereses a corto plazo de deudas con entidades de crédito","527","account.data_account_type_current_liabilities","l10n_es.account_chart_template_common","l10n_es.account_group_527","False" "account_common_528","Intereses a corto plazo de deudas","528","account.data_account_type_current_liabilities","l10n_es.account_chart_template_common","l10n_es.account_group_528","False" "account_common_5290","Provisión a corto plazo por retribuciones al personal","5290","account.data_account_type_current_liabilities","l10n_es.account_chart_template_common","l10n_es.account_group_5290","True" diff --git a/addons/l10n_es/data/account_tax_data.xml b/addons/l10n_es/data/account_tax_data.xml index 327144f6fe1..0bad56c5caf 100644 --- a/addons/l10n_es/data/account_tax_data.xml +++ b/addons/l10n_es/data/account_tax_data.xml @@ -225,6 +225,7 @@ <field name="amount_type">group</field> <field name="amount" eval="100"/> <field name="children_tax_ids" eval="[(6, 0, [ref('account_tax_template_p_iva21_sp_in_1'), ref('account_tax_template_p_iva21_sp_in_2')])]"/> + <field name="tag_ids" eval="[(6, False, [ref('mod_303_10_11'), ref('mod_303_14_15_purchase'), ref('mod_303_36_37'), ref('mod_303_40_41')])]"/> <field name="tax_group_id" ref="tax_group_iva_21"/> </record> <record id="account_tax_template_p_iva21_ic_bc_1" model="account.tax.template"> @@ -257,7 +258,8 @@ <field name="description">P_IVA21_IC_BC</field> <field name="amount_type">group</field> <field name="amount" eval="100"/> - <field name="children_tax_ids" eval="[(6, 0, [ref('account_tax_template_p_iva21_ic_bc_1'), ref('account_tax_template_p_iva21_ic_bc_2')])]"/> + <field name="children_tax_ids" eval="[(6, 0, [ref('account_tax_template_p_iva21_ic_bc_1'), ref('account_tax_template_p_iva21_ic_bc_2'), ])]"/> + <field name="tag_ids" eval="[(6, False, [ref('mod_303_36_37'), ref('mod_303_40_41'), ref('mod_303_10_11'), ref('mod_303_14_15_purchase')])]"/> <field name="chart_template_id" ref="l10n_es.account_chart_template_common"/> <field name="type_tax_use">purchase</field> <field name="name">IVA 21% Adquisición Intracomunitaria. Bienes corrientes</field> @@ -296,6 +298,7 @@ <field name="amount_type">group</field> <field name="amount" eval="100"/> <field name="children_tax_ids" eval="[(6, 0, [ref('account_tax_template_p_iva21_ic_bi_1'), ref('account_tax_template_p_iva21_ic_bi_2')])]"/> + <field name="tag_ids" eval="[(6, False, [ref('mod_303_38_39'), ref('mod_303_40_41'), ref('mod_303_10_11'), ref('mod_303_14_15_purchase')])]"/> <field name="chart_template_id" ref="l10n_es.account_chart_template_common"/> <field name="tax_group_id" ref="tax_group_iva_21"/> </record> @@ -368,6 +371,7 @@ <field name="amount_type">group</field> <field name="type_tax_use">purchase</field> <field name="children_tax_ids" eval="[(6, 0, [ref('account_tax_template_p_iva4_sp_ex_1'), ref('account_tax_template_p_iva4_sp_ex_2')])]"/> + <field name="tag_ids" eval="[(6, False, [ref('mod_303_12_13'), ref('mod_303_14_15_purchase'), ref('mod_303_28_29'), ref('mod_303_40_41')])]"/> <field name="name">IVA 4% Adquisición de servicios extracomunitarios</field> <field name="tax_group_id" ref="tax_group_iva_4"/> </record> @@ -403,6 +407,7 @@ <field name="chart_template_id" ref="l10n_es.account_chart_template_common"/> <field name="amount_type">group</field> <field name="children_tax_ids" eval="[(6, 0, [ref('account_tax_template_p_iva10_sp_ex_1'), ref('account_tax_template_p_iva10_sp_ex_2')])]"/> + <field name="tag_ids" eval="[(6, False, [ref('mod_303_12_13'), ref('mod_303_14_15_purchase'), ref('mod_303_28_29'), ref('mod_303_40_41')])]"/> <field name="type_tax_use">purchase</field> <field name="name">IVA 10% Adquisición de servicios extracomunitarios</field> <field name="tax_group_id" ref="tax_group_iva_10"/> @@ -441,6 +446,7 @@ <field name="chart_template_id" ref="l10n_es.account_chart_template_common"/> <field name="amount_type">group</field> <field name="children_tax_ids" eval="[(6, 0, [ref('account_tax_template_p_iva21_sp_ex_1'), ref('account_tax_template_p_iva21_sp_ex_2')])]"/> + <field name="tag_ids" eval="[(6, False, [ref('mod_303_12_13'), ref('mod_303_14_15_purchase'), ref('mod_303_28_29'), ref('mod_303_40_41')])]"/> <field name="tax_group_id" ref="tax_group_iva_21"/> </record> <record id="account_tax_template_p_iva4_ic_bc_1" model="account.tax.template"> @@ -477,6 +483,7 @@ <field name="type_tax_use">purchase</field> <field name="name">IVA 4% Adquisición Intracomunitario. Bienes corrientes</field> <field name="children_tax_ids" eval="[(6, 0, [ref('account_tax_template_p_iva4_ic_bc_1'), ref('account_tax_template_p_iva4_ic_bc_2')])]"/> + <field name="tag_ids" eval="[(6, False, [ref('mod_303_36_37'), ref('mod_303_40_41'), ref('mod_303_10_11'), ref('mod_303_14_15_purchase')])]"/> <field name="tax_group_id" ref="tax_group_iva_4"/> </record> <record id="account_tax_template_p_iva4_ic_bi_1" model="account.tax.template"> @@ -514,6 +521,7 @@ <field name="type_tax_use">purchase</field> <field name="name">IVA 4% Adquisición Intracomunitario. Bienes de inversión</field> <field name="children_tax_ids" eval="[(6, 0, [ref('account_tax_template_p_iva4_ic_bi_1'), ref('account_tax_template_p_iva4_ic_bi_2')])]"/> + <field name="tag_ids" eval="[(6, False, [ref('mod_303_38_39'), ref('mod_303_40_41'), ref('mod_303_10_11'), ref('mod_303_14_15_purchase')])]"/> <field name="tax_group_id" ref="tax_group_iva_4"/> </record> <record id="account_tax_template_p_iva10_ic_bc_1" model="account.tax.template"> @@ -550,6 +558,7 @@ <field name="type_tax_use">purchase</field> <field name="name">IVA 10% Adquisición Intracomunitario. Bienes corrientes</field> <field name="children_tax_ids" eval="[(6, 0, [ref('account_tax_template_p_iva10_ic_bc_1'), ref('account_tax_template_p_iva10_ic_bc_2')])]"/> + <field name="tag_ids" eval="[(6, False, [ref('mod_303_36_37'), ref('mod_303_40_41'), ref('mod_303_10_11'), ref('mod_303_14_15_purchase')])]"/> <field name="tax_group_id" ref="tax_group_iva_10"/> </record> <record id="account_tax_template_p_iva10_ic_bi_1" model="account.tax.template"> @@ -586,6 +595,7 @@ <field name="type_tax_use">purchase</field> <field name="name">IVA 10% Adquisición Intracomunitario. Bienes de inversión</field> <field name="children_tax_ids" eval="[(6, 0, [ref('account_tax_template_p_iva10_ic_bi_1'), ref('account_tax_template_p_iva10_ic_bi_2')])]"/> + <field name="tag_ids" eval="[(6, False, [ref('mod_303_38_39'), ref('mod_303_40_41'), ref('mod_303_10_11'), ref('mod_303_14_15_purchase')])]"/> <field name="tax_group_id" ref="tax_group_iva_10"/> </record> <record id="account_tax_template_s_iva0_sp_i" model="account.tax.template"> @@ -1248,6 +1258,7 @@ <field name="type_tax_use">purchase</field> <field name="name">IVA 10% Adquisición de servicios intracomunitarios</field> <field name="children_tax_ids" eval="[(6, 0, [ref('account_tax_template_p_iva10_sp_in_1'), ref('account_tax_template_p_iva10_sp_in_2')])]"/> + <field name="tag_ids" eval="[(6, False, [ref('mod_303_10_11'), ref('mod_303_14_15_purchase'), ref('mod_303_36_37'), ref('mod_303_40_41')])]"/> <field name="tax_group_id" ref="tax_group_iva_10"/> </record> <record id="account_tax_template_p_iva4_sp_in_1" model="account.tax.template"> @@ -1284,6 +1295,7 @@ <field name="type_tax_use">purchase</field> <field name="name">IVA 4% Adquisición de servicios intracomunitarios</field> <field name="children_tax_ids" eval="[(6, 0, [ref('account_tax_template_p_iva4_sp_in_1'), ref('account_tax_template_p_iva4_sp_in_2')])]"/> + <field name="tag_ids" eval="[(6, False, [ref('mod_303_10_11'), ref('mod_303_14_15_purchase'), ref('mod_303_36_37'), ref('mod_303_40_41')])]"/> <field name="tax_group_id" ref="tax_group_iva_4"/> </record> <record id="account_tax_template_p_irpf21te" model="account.tax.template"> @@ -1390,6 +1402,7 @@ <field name="amount_type">group</field> <field name="amount" eval="100"/> <field name="children_tax_ids" eval="[(6, 0, [ref('account_tax_template_p_iva4_isp_1'), ref('account_tax_template_p_iva4_isp_2')])]"/> + <field name="tag_ids" eval="[(6, False, [ref('mod_303_28_29'), ref('mod_303_40_41'), ref('mod_303_12_13'), ref('mod_303_14_15_purchase')])]"/> <field name="chart_template_id" ref="l10n_es.account_chart_template_common"/> <field name="tax_group_id" ref="tax_group_iva_4"/> </record> @@ -1426,6 +1439,7 @@ <field name="amount_type">group</field> <field name="amount" eval="100"/> <field name="children_tax_ids" eval="[(6, 0, [ref('account_tax_template_p_iva10_isp_1'), ref('account_tax_template_p_iva10_isp_2')])]"/> + <field name="tag_ids" eval="[(6, False, [ref('mod_303_28_29'), ref('mod_303_40_41'), ref('mod_303_12_13'), ref('mod_303_14_15_purchase')])]"/> <field name="chart_template_id" ref="l10n_es.account_chart_template_common"/> <field name="tax_group_id" ref="tax_group_iva_10"/> </record> @@ -1462,6 +1476,7 @@ <field name="amount_type">group</field> <field name="amount" eval="100"/> <field name="children_tax_ids" eval="[(6, 0, [ref('account_tax_template_p_iva21_isp_1'), ref('account_tax_template_p_iva21_isp_2')])]"/> + <field name="tag_ids" eval="[(6, False, [ref('mod_303_28_29'), ref('mod_303_40_41'), ref('mod_303_12_13'), ref('mod_303_14_15_purchase')])]"/> <field name="chart_template_id" ref="l10n_es.account_chart_template_common"/> <field name="tax_group_id" ref="tax_group_iva_21"/> </record> diff --git a/addons/l10n_mn/__manifest__.py b/addons/l10n_mn/__manifest__.py index a99552f8ee3..5b6d10689bf 100644 --- a/addons/l10n_mn/__manifest__.py +++ b/addons/l10n_mn/__manifest__.py @@ -5,6 +5,7 @@ "name" : "Mongolia - Accounting", "version" : "1.0", 'category': 'Localization', + "author" : "BumanIT LLC, Odoo S.A", "description": """ This is the module to manage the accounting chart for Mongolia. =============================================================== diff --git a/addons/l10n_mx/data/account.account.template.csv b/addons/l10n_mx/data/account.account.template.csv index b9cd4a41d36..316cab62337 100644 --- a/addons/l10n_mx/data/account.account.template.csv +++ b/addons/l10n_mx/data/account.account.template.csv @@ -8,7 +8,7 @@ "cuenta115_02","Materia prima y materiales","115.02.01","account.data_account_type_current_assets","l10n_mx.mx_coa","l10n_mx.account_tag_115_02","False" "cuenta115_03","Producción en proceso","115.03.01","account.data_account_type_current_assets","l10n_mx.mx_coa","l10n_mx.account_tag_115_03","False" "cuenta115_04","Productos terminados","115.04.01","account.data_account_type_current_assets","l10n_mx.mx_coa","l10n_mx.account_tag_115_04","False" -"cuenta115_05","Mercancías en tránsito","115.05.01","account.data_account_type_current_assets","l10n_mx.mx_coa","l10n_mx.account_tag_115_05","False" +"cuenta115_05","Mercancías en tránsito","115.05.01","account.data_account_type_current_assets","l10n_mx.mx_coa","l10n_mx.account_tag_115_05","True" "cuenta115_06","Mercancías en poder de terceros","115.06.01","account.data_account_type_current_assets","l10n_mx.mx_coa","l10n_mx.account_tag_115_06","False" "cuenta118_01","IVA acreditable pagado","118.01.01","account.data_account_type_current_assets","l10n_mx.mx_coa","l10n_mx.account_tag_118_01","False" "cuenta119_01","IVA pendiente de pago","119.01.01","account.data_account_type_current_assets","l10n_mx.mx_coa","l10n_mx.account_tag_119_01","False" diff --git a/addons/mail/models/mail_activity.py b/addons/mail/models/mail_activity.py index bf0b0141604..14ebbb63de2 100644 --- a/addons/mail/models/mail_activity.py +++ b/addons/mail/models/mail_activity.py @@ -313,6 +313,12 @@ class MailActivity(models.Model): @api.model def create(self, values): activity = super(MailActivity, self).create(values) + need_sudo = False + try: # in multicompany, reading the partner might break + partner_id = activity_user.user_id.partner_id.id + except exceptions.AccessError: + need_sudo = True + partner_id = activity_user.user_id.sudo().partner_id.id # send a notification to assigned user; in case of manually done activity also check # target has rights on document otherwise we prevent its creation. Automated activities @@ -321,9 +327,12 @@ class MailActivity(models.Model): if not activity.automated: activity._check_access_assignation() if not self.env.context.get('mail_activity_quick_update', False): - activity.action_notify() + if need_sudo: + activity.sudo().action_notify() + else: + activity.action_notify() - self.env[activity.res_model].browse(activity.res_id).message_subscribe(partner_ids=[activity.user_id.partner_id.id]) + self.env[activity.res_model].browse(activity.res_id).message_subscribe(partner_ids=[partner_id]) if activity.date_deadline <= fields.Date.today(): self.env['bus.bus'].sendone( (self._cr.dbname, 'res.partner', activity.user_id.partner_id.id), diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py index 9b450b9b2a9..98e46ba84ad 100644 --- a/addons/mail/models/mail_thread.py +++ b/addons/mail/models/mail_thread.py @@ -288,7 +288,7 @@ class MailThread(models.AbstractModel): # track values if not self._context.get('mail_notrack'): - if 'lang' not in self._context: + if not self._context.get('lang'): track_threads = threads.with_context(lang=self.env.user.lang) else: track_threads = threads diff --git a/addons/mail/static/src/js/services/mail_notification_manager.js b/addons/mail/static/src/js/services/mail_notification_manager.js index 2f689a85fbc..ddae919454d 100644 --- a/addons/mail/static/src/js/services/mail_notification_manager.js +++ b/addons/mail/static/src/js/services/mail_notification_manager.js @@ -503,7 +503,7 @@ MailManager.include({ } this._removeChannel(channel); this._mailBus.trigger('unsubscribe_from_channel', data.id); - this.do_notify(_("Unsubscribed"), message); + this.do_notify(_t("Unsubscribed"), message); } }, /** diff --git a/addons/mail/static/src/scss/composer.scss b/addons/mail/static/src/scss/composer.scss index b265979065b..b5378c3c1a7 100644 --- a/addons/mail/static/src/scss/composer.scss +++ b/addons/mail/static/src/scss/composer.scss @@ -227,6 +227,8 @@ .o_mail_emoji_container { @include o-position-absolute(auto, 0, 100%, 0); border-bottom-width: 1px; + max-height: calc(100vh - 100%); + overflow-y: auto; } } .o_composer_input { diff --git a/addons/mail/views/mail_templates.xml b/addons/mail/views/mail_templates.xml index ea742a29fda..af89eb6b82d 100644 --- a/addons/mail/views/mail_templates.xml +++ b/addons/mail/views/mail_templates.xml @@ -145,7 +145,7 @@ </template> <template id="message_origin_link"> - <p>This <t t-esc="self._description.lower()"/> has been <span t-if="edit">modified</span><span t-if="not edit">created</span> from: + <p>This <t t-esc="self.env['ir.model']._get(self._name).name.lower()"/> has been <span t-if="edit">modified</span><span t-if="not edit">created</span> from: <t t-foreach="origin" t-as="o"> <a href="#" t-att-data-oe-model="o._name" t-att-data-oe-id="o.id"> <t t-esc="o.display_name"/></a><span t-if="origin.ids[-1:] != o.ids">, </span> </t> diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py index a003f2e42c6..95ae6372c41 100644 --- a/addons/mail/wizard/mail_compose_message.py +++ b/addons/mail/wizard/mail_compose_message.py @@ -306,7 +306,6 @@ class MailComposer(models.TransientModel): 'attachment_ids': [attach.id for attach in self.attachment_ids], 'author_id': self.author_id.id, 'email_from': self.email_from, - 'reply_to': self.reply_to, 'record_name': self.record_name, 'no_auto_thread': self.no_auto_thread, 'mail_server_id': self.mail_server_id.id, diff --git a/addons/mass_mailing/static/src/scss/themes/theme_basic.scss b/addons/mass_mailing/static/src/scss/themes/theme_basic.scss index 00f3a6304bc..d65ea077f5f 100644 --- a/addons/mass_mailing/static/src/scss/themes/theme_basic.scss +++ b/addons/mass_mailing/static/src/scss/themes/theme_basic.scss @@ -1,3 +1,32 @@ + +// TODO remove in master +// Themes scss have to use bg-variant and text-emphasis-variant mixins of BS4 +// to properly work. That use was introduced as a fix in 12.0... but the BS4 +// mixins are unavailable in the mailing assets. As a stable fix cannot +// introduce static code which requires an XML update to not crash, the mixins +// were duplicated here instead. +@mixin bg-variant($parent, $color) { + #{$parent} { + background-color: $color !important; + } + a#{$parent}, + button#{$parent} { + &:hover, &:focus { + background-color: darken($color, 10%) !important; + } + } +} +@mixin text-emphasis-variant($parent, $color) { + #{$parent} { + color: $color !important; + } + a#{$parent} { + &:hover, &:focus { + color: darken($color, 10%) !important; + } + } +} + // ============================ // Mass Mailing "Theme Basic" // ============================ diff --git a/addons/mass_mailing/static/src/scss/themes/theme_default.scss b/addons/mass_mailing/static/src/scss/themes/theme_default.scss index afd4057cfc9..4d49a7ef22c 100644 --- a/addons/mass_mailing/static/src/scss/themes/theme_default.scss +++ b/addons/mass_mailing/static/src/scss/themes/theme_default.scss @@ -24,19 +24,10 @@ $o-mm-def-btn-bg : $o-mm-def-color-gamma; $o-mm-def-btn-text : $o-mm-def-color-beta; // ===== Colors ===== -.bg-beta { - background-color: $o-mm-def-color-zeta; - color: $o-mm-def-color-beta; -} -.text-beta { - color: $o-mm-def-color-zeta; -} -.bg-delta { - background-color: $o-mm-def-color-eta; -} -.bg-gray-lighter { - background-color: $o-mm-def-color-alpha; -} +@include bg-variant(".bg-beta", $o-mm-def-color-zeta); +@include text-emphasis-variant(".text-beta", $o-mm-def-color-zeta); +@include bg-variant(".bg-delta", $o-mm-def-color-eta); +@include bg-variant(".bg-gray-lighter", $o-mm-def-color-alpha); td:not([align]) { // Default browser style but needed so that alignment works on some mail diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py index e3b3fc2a611..e2759f29c87 100644 --- a/addons/mrp/models/mrp_production.py +++ b/addons/mrp/models/mrp_production.py @@ -209,7 +209,7 @@ class MrpProduction(models.Model): def _compute_picking_ids(self): for order in self: order.picking_ids = self.env['stock.picking'].search([ - ('group_id', '=', order.procurement_group_id.id), + ('group_id', '=', order.procurement_group_id.id), ('group_id', '!=', False), ]) order.delivery_count = len(order.picking_ids) diff --git a/addons/mrp/wizard/change_production_qty.py b/addons/mrp/wizard/change_production_qty.py index e98364f9b93..c4cd57815ad 100644 --- a/addons/mrp/wizard/change_production_qty.py +++ b/addons/mrp/wizard/change_production_qty.py @@ -59,7 +59,8 @@ class ChangeProductionQty(models.TransientModel): continue move = production.move_raw_ids.filtered(lambda x: x.bom_line_id.id == line.id and x.state not in ('done', 'cancel')) if move: - old_qty = move[0].product_uom_qty + move = move[0] + old_qty = move.product_uom_qty else: old_qty = 0 iterate_key = production._get_document_iterate_key(move) diff --git a/addons/partner_autocomplete/models/res_partner.py b/addons/partner_autocomplete/models/res_partner.py index 9d357fec6d8..1f5a9942c93 100644 --- a/addons/partner_autocomplete/models/res_partner.py +++ b/addons/partner_autocomplete/models/res_partner.py @@ -95,7 +95,7 @@ class ResPartner(models.Model): }) try: return jsonrpc(url=url, params=params, timeout=timeout), False - except (ConnectionError, HTTPError, exceptions.AccessError) as exception: + except (ConnectionError, HTTPError, exceptions.AccessError, exceptions.UserError) as exception: _logger.error('Autocomplete API error: %s' % str(exception)) return False, str(exception) except InsufficientCreditError as exception: diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py index 7254f245dc7..4078f58ded2 100644 --- a/addons/point_of_sale/models/pos_order.py +++ b/addons/point_of_sale/models/pos_order.py @@ -328,7 +328,7 @@ class PosOrder(models.Model): res.append({'data_type': 'tax', 'values': data}) return res - def _create_account_move_line(self, session=None): + def _prepare_account_move_and_lines(self, session=None): # Tricky, via the workflow, we only have one id in the ids variable """Create a account move line of order grouped by products or not.""" @@ -472,6 +472,17 @@ class PosOrder(models.Model): if self and order.company_id.anglo_saxon_accounting: add_anglosaxon_lines(grouped_data) + return { + 'grouped_data': grouped_data, + 'move': move, + } + + def _create_account_move_line(self, session=None): + vals = self._prepare_account_move_and_lines(session) + + grouped_data = vals['grouped_data'] + move = vals['move'] + all_lines = [] for group_key, group_data in grouped_data.items(): for value in group_data: @@ -1149,6 +1160,7 @@ class PosOrderLine(models.Model): class PosOrderLineLot(models.Model): _name = "pos.pack.operation.lot" _description = "Specify product lot/serial number in pos order line" + _rec_name = "lot_name" pos_order_line_id = fields.Many2one('pos.order.line') order_id = fields.Many2one('pos.order', related="pos_order_line_id.order_id", readonly=False) diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index f5af456fbeb..c70257057aa 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -2534,9 +2534,36 @@ exports.Order = Backbone.Model.extend({ }), 0), this.pos.currency.rounding); }, get_total_tax: function() { - return round_pr(this.orderlines.reduce((function(sum, orderLine) { - return sum + orderLine.get_tax(); - }), 0), this.pos.currency.rounding); + if (this.pos.company.tax_calculation_rounding_method === "round_globally") { + // As always, we need: + // 1. For each tax, sum their amount across all order lines + // 2. Round that result + // 3. Sum all those rounded amounts + var groupTaxes = {}; + this.orderlines.each(function (line) { + var taxDetails = line.get_tax_details(); + var taxIds = Object.keys(taxDetails); + for (var t = 0; t<taxIds.length; t++) { + var taxId = taxIds[t]; + if (!(taxId in groupTaxes)) { + groupTaxes[taxId] = 0; + } + groupTaxes[taxId] += taxDetails[taxId]; + } + }); + + var sum = 0; + var taxIds = Object.keys(groupTaxes); + for (var j = 0; j<taxIds.length; j++) { + var taxAmount = groupTaxes[taxIds[j]]; + sum += round_pr(taxAmount, this.pos.currency.rounding); + } + return sum; + } else { + return round_pr(this.orderlines.reduce((function(sum, orderLine) { + return sum + orderLine.get_tax(); + }), 0), this.pos.currency.rounding); + } }, get_total_paid: function() { return round_pr(this.paymentlines.reduce((function(sum, paymentLine) { diff --git a/addons/point_of_sale/views/pos_order_view.xml b/addons/point_of_sale/views/pos_order_view.xml index 6bccce47ce5..088bbd77f37 100644 --- a/addons/point_of_sale/views/pos_order_view.xml +++ b/addons/point_of_sale/views/pos_order_view.xml @@ -55,6 +55,7 @@ <field name="price_subtotal_incl" invisible="1" widget="monetary" force_save="1"/> <field name="tax_ids_after_fiscal_position" widget="many2many_tags" string="Taxes"/> <field name="tax_ids" widget="many2many_tags" invisible="1"/> + <field name="pack_lot_ids" widget="many2many_tags" groups="stock.group_production_lot"/> <field name="notice"/> </group> </form> diff --git a/addons/portal/models/mail_message.py b/addons/portal/models/mail_message.py index afe97f1b8ca..4a0d09580fb 100644 --- a/addons/portal/models/mail_message.py +++ b/addons/portal/models/mail_message.py @@ -20,6 +20,11 @@ class MailMessage(models.Model): message_values = self.read(fields_list) message_tree = dict((m.id, m) for m in self.sudo()) self._message_read_dict_postprocess(message_values, message_tree) + IrAttachmentSudo = self.env['ir.attachment'].sudo() + for message in message_values: + for attachment in message.get('attachment_ids', []): + if not attachment.get('access_token'): + attachment['access_token'] = IrAttachmentSudo.browse(attachment['id']).generate_access_token()[0] return message_values @api.model diff --git a/addons/portal/static/src/xml/portal_chatter.xml b/addons/portal/static/src/xml/portal_chatter.xml index f090965bde2..7d80fc30410 100644 --- a/addons/portal/static/src/xml/portal_chatter.xml +++ b/addons/portal/static/src/xml/portal_chatter.xml @@ -72,7 +72,7 @@ <div class="o_portal_chatter_attachments"> <div class="col-lg-2 col-md-3 text-center" t-foreach='message.attachment_ids' t-as='attachment'> - <a t-attf-href="/web/content/#{attachment.id}?download=true" target="_blank"> + <a t-attf-href="/web/content/#{attachment.id}?download=true&access_token=#{attachment.access_token}" target="_blank"> <div class='oe_attachment_embedded o_image' t-att-title="attachment.name" t-att-data-mimetype="attachment.mimetype" t-attf-data-src="/web/image/#{attachment.id}/100x80"/> <div class='oe_attachment_name'><t t-raw='attachment.name'/></div> </a> diff --git a/addons/portal/views/portal_templates.xml b/addons/portal/views/portal_templates.xml index 9ded0d59297..99ea33b7289 100644 --- a/addons/portal/views/portal_templates.xml +++ b/addons/portal/views/portal_templates.xml @@ -399,7 +399,7 @@ <input type="text" name="city" t-attf-class="form-control #{error.get('city') and 'is-invalid' or ''}" t-att-value="city or partner.city" /> </div> <div t-attf-class="form-group #{error.get('zip') and 'o_has_error' or ''} col-xl-6"> - <label class="col-form-label" for="zipcode">Zip / Postal Code</label> + <label class="col-form-label label-optional" for="zipcode">Zip / Postal Code</label> <input type="text" name="zipcode" t-attf-class="form-control #{error.get('zip') and 'is-invalid' or ''}" t-att-value="zipcode or partner.zip" /> </div> <div t-attf-class="form-group #{error.get('country_id') and 'o_has_error' or ''} col-xl-6"> diff --git a/addons/project/views/project_views.xml b/addons/project/views/project_views.xml index b97443b410f..d9969a3e205 100644 --- a/addons/project/views/project_views.xml +++ b/addons/project/views/project_views.xml @@ -537,7 +537,6 @@ <field name="priority"/> <field name="stage_id" options='{"group_by_tooltip": {"legend_priority": "Use of stars"}}'/> <field name="user_id"/> - <field name="user_email"/> <field name="sequence"/> <field name="date_deadline"/> <field name="message_needaction_counter"/> diff --git a/addons/purchase_stock/models/stock_rule.py b/addons/purchase_stock/models/stock_rule.py index 26b1159bfb5..cf5b5cc4872 100644 --- a/addons/purchase_stock/models/stock_rule.py +++ b/addons/purchase_stock/models/stock_rule.py @@ -270,7 +270,7 @@ class StockRule(models.Model): 'partner_id': partner.id, 'picking_type_id': self.picking_type_id.id, 'company_id': company_id.id, - 'currency_id': partner.with_context(force_company=company_id.id).property_purchase_currency_id.id or self.env.user.company_id.currency_id.id, + 'currency_id': partner.with_context(force_company=company_id.id).property_purchase_currency_id.id or company_id.currency_id.id, 'dest_address_id': values.get('partner_id', False), 'origin': ', '.join(origins), 'payment_term_id': partner.with_context(force_company=company_id.id).property_supplier_payment_term_id.id, diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py index 24dfedf38a6..312b0bc4c0a 100644 --- a/addons/sale/models/sale.py +++ b/addons/sale/models/sale.py @@ -1031,7 +1031,8 @@ class SaleOrderLine(models.Model): for line in self: fpos = line.order_id.fiscal_position_id or line.order_id.partner_id.property_account_position_id # If company_id is set, always filter taxes by the company - taxes = line.product_id.taxes_id.filtered(lambda r: not line.company_id or r.company_id == line.company_id) + line_company_id = line.company_id or line.order_id.company_id + taxes = line.product_id.taxes_id.filtered(lambda r: not line_company_id or r.company_id == line_company_id) line.tax_id = fpos.map_tax(taxes, line.product_id, line.order_id.partner_shipping_id) if fpos else taxes @api.model diff --git a/addons/sale/tests/test_sale_order.py b/addons/sale/tests/test_sale_order.py index cd115e5bbab..b5dcb29f475 100644 --- a/addons/sale/tests/test_sale_order.py +++ b/addons/sale/tests/test_sale_order.py @@ -239,6 +239,59 @@ class TestSaleOrder(TestCommonSaleNoChart): self.sale_order.amount_untaxed + self.sale_order.amount_tax, 'Taxes should be applied') + def test_so_create_multicompany(self): + # Preparing test Data + user_demo = self.env.ref('base.user_demo') + company_1 = self.env.ref('base.main_company') + company_2 = self.env['res.company'].create({ + 'name': 'company 2', + 'parent_id': company_1.id, + }) + user_demo.write({ + 'groups_id': [(4, self.env.ref('sales_team.group_sale_manager').id, False)], + 'company_ids': [(6, False, [company_1.id])], + 'company_id': company_1.id, + }) + + so_partner = self.env.ref('base.res_partner_2') + so_partner.write({ + 'property_account_position_id': False, + }) + + tax_company_1 = self.env['account.tax'].create({ + 'name': 'T1', + 'amount': 90, + 'company_id': company_1.id, + }) + + tax_company_2 = self.env['account.tax'].create({ + 'name': 'T2', + 'amount': 90, + 'company_id': company_2.id, + }) + + product_shared = self.env['product.template'].create({ + 'name': 'shared product', + 'taxes_id': [(6, False, [tax_company_1.id, tax_company_2.id])], + }) + + # Use case + so_1 = self.env['sale.order'].sudo(user_demo.id).create({ + 'partner_id': so_partner.id, + 'company_id': company_1.id, + }) + so_1.invalidate_cache() + + # This is what is done when importing the csv lines (on sale.order): + # id,order_line/product_id + # __export__.sale_order_37_1bb960ba,Product name + so_1.write({ + 'order_line': [(0, False, {'product_id': product_shared.product_variant_id.id, 'order_id': so_1.id})], + }) + + self.assertEqual(set(so_1.order_line.tax_id.ids), set([tax_company_1.id]), + 'Only taxes from the right company are put by default') + def test_reconciliation_with_so(self): # create SO so = self.env['sale.order'].create({ diff --git a/addons/sale/views/sale_portal_templates.xml b/addons/sale/views/sale_portal_templates.xml index b6176f1d764..357a2773064 100644 --- a/addons/sale/views/sale_portal_templates.xml +++ b/addons/sale/views/sale_portal_templates.xml @@ -463,12 +463,11 @@ <div t-if="line.discount >= 0" t-field="line.price_unit" - t-options='{"widget": "monetary", "display_currency": sale_order.pricelist_id.currency_id}' t-att-style="line.discount and 'text-decoration: line-through' or None" t-att-class="(line.discount and 'text-danger' or '') + ' text-right'" /> <div t-if="line.discount"> - <t t-esc="(1-line.discount / 100.0) * line.price_unit" t-options='{"widget": "monetary", "display_currency": sale_order.pricelist_id.currency_id}'/> + <t t-esc="(1-line.discount / 100.0) * line.price_unit" t-options='{"widget": "float", "decimal_precision": "Product Price"}'/> </div> </td> <td t-if="display_discount" t-attf-class="text-right {{ 'd-none d-sm-table-cell' if report_type == 'html' else '' }}"> diff --git a/addons/stock/models/res_config_settings.py b/addons/stock/models/res_config_settings.py index 3eca680cce7..f5116ebf7e6 100644 --- a/addons/stock/models/res_config_settings.py +++ b/addons/stock/models/res_config_settings.py @@ -1,7 +1,8 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo import api, fields, models +from odoo import api, fields, models, _ +from odoo.exceptions import UserError class ResConfigSettings(models.TransientModel): @@ -64,6 +65,13 @@ class ResConfigSettings(models.TransientModel): def _onchange_group_stock_production_lot(self): if not self.group_stock_production_lot: self.group_lot_on_delivery_slip = False + tracked_products = self.env['product.template'].search([('tracking', 'in', ['lot', 'serial']),]) + if not self.group_stock_production_lot and tracked_products: + names = ", ".join(tracked_products.mapped('display_name') if len(tracked_products) <= 10 + else tracked_products[:10].mapped('display_name') + ["..."]) + raise UserError(_("You should not remove the 'lots and serial numbers' " + "option while the following products are still tracked by lot " + "or serial number:\n %s") % names) @api.onchange('group_stock_adv_location') def onchange_adv_location(self): diff --git a/addons/stock/security/stock_security.xml b/addons/stock/security/stock_security.xml index 87c63386ca7..7a377af6abb 100644 --- a/addons/stock/security/stock_security.xml +++ b/addons/stock/security/stock_security.xml @@ -94,7 +94,7 @@ <field name="name">stock_move multi-company</field> <field name="model_id" search="[('model','=','stock.move')]" model="ir.model"/> <field name="global" eval="True"/> - <field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field> + <field name="domain_force">['|','|',('company_id','=',False),('company_id','child_of',[user.company_id.id]),('location_dest_id.company_id', '=', False)]</field> </record> <record model="ir.rule" id="stock_move_line_rule"> diff --git a/addons/stock/views/product_views.xml b/addons/stock/views/product_views.xml index 3aea1854604..a4a4642eb7a 100644 --- a/addons/stock/views/product_views.xml +++ b/addons/stock/views/product_views.xml @@ -180,6 +180,7 @@ <record model="ir.ui.view" id="product_form_view_procurement_button"> <field name="name">product.product.procurement</field> <field name="model">product.product</field> + <field name="groups_id" eval="[(4, ref('stock.group_stock_user'))]"/> <field name="inherit_id" ref="product.product_normal_form_view"/> <field name="arch" type="xml"> <data> @@ -266,6 +267,7 @@ <record model="ir.ui.view" id="product_template_form_view_procurement_button"> <field name="name">product.template_procurement</field> <field name="model">product.template</field> + <field name="groups_id" eval="[(4, ref('stock.group_stock_user'))]"/> <field name="inherit_id" ref="product.product_template_only_form_view"/> <field name="arch" type="xml"> <data> diff --git a/addons/stock_dropshipping/__manifest__.py b/addons/stock_dropshipping/__manifest__.py index de26d991087..9d092675cf2 100644 --- a/addons/stock_dropshipping/__manifest__.py +++ b/addons/stock_dropshipping/__manifest__.py @@ -21,7 +21,7 @@ internal transfer document is needed. """, 'depends': ['sale_purchase', 'sale_stock'], - 'data': ['data/stock_data.xml'], + 'data': ['data/stock_data.xml', 'views/sale_order_views.xml'], 'installable': True, 'auto_install': False, } diff --git a/addons/stock_dropshipping/views/sale_order_views.xml b/addons/stock_dropshipping/views/sale_order_views.xml new file mode 100644 index 00000000000..b06b48ccab9 --- /dev/null +++ b/addons/stock_dropshipping/views/sale_order_views.xml @@ -0,0 +1,24 @@ +<?xml version="1.0"?> +<odoo> + <data> + <record id="view_order_form_inherit_sale_stock" model="ir.ui.view"> + <field name="name">sale.order.form.sale.dropshipping</field> + <field name="model">sale.order</field> + <field name="inherit_id" ref="sale.view_order_form"/> + <field name="arch" type="xml"> + <xpath expr="//page/field[@name='order_line']/form//field[@name='product_updatable']" position="after"> + <field name="purchase_line_count" invisible="1"/> + </xpath> + <xpath expr="//page/field[@name='order_line']/form//field[@name='product_id']" position="attributes"> + <attribute name="attrs">{'readonly': ['|', ('product_updatable', '=', False), ('purchase_line_count', '>', 0)], 'required': [('display_type', '=', False)],}</attribute> + </xpath> + <xpath expr="//page/field[@name='order_line']/tree/field[@name='product_updatable']" position="after"> + <field name="purchase_line_count" invisible="1"/> + </xpath> + <xpath expr="//page/field[@name='order_line']/tree/field[@name='product_id']" position="attributes"> + <attribute name="attrs">{'readonly': ['|', ('product_updatable', '=', False), ('purchase_line_count', '>', 0)], 'required': [('display_type', '=', False)],}</attribute> + </xpath> + </field> + </record> + </data> +</odoo> diff --git a/addons/test_mail/tests/test_performance.py b/addons/test_mail/tests/test_performance.py index 8a60f15b388..a80047ee874 100644 --- a/addons/test_mail/tests/test_performance.py +++ b/addons/test_mail/tests/test_performance.py @@ -488,7 +488,7 @@ class TestHeavyMailPerformance(TransactionCase): }) self.assertEqual(rec.message_partner_ids, self.partners | self.env.user.partner_id) - with self.assertQueryCount(__system__=61, emp=76): # com runbot: 61 - 76 // test_mail only: 60 - 72 + with self.assertQueryCount(__system__=62, emp=76): # com runbot: 61 - 76 // test_mail only: 60 - 72 rec.write({'user_id': self.user_portal.id}) self.assertEqual(rec.message_partner_ids, self.partners | self.env.user.partner_id | self.user_portal.partner_id) diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index 6d382c92333..7dfee54294b 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -198,11 +198,11 @@ def concat_xml(file_list): :param list(str) file_list: list of files to check :returns: (concatenation_result, checksum) - :rtype: (str, str) + :rtype: (bytes, str) """ checksum = hashlib.new('sha1') if not file_list: - return '', checksum.hexdigest() + return b'', checksum.hexdigest() root = None for fname in file_list: diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js index e576e094e30..c7624bd83a4 100644 --- a/addons/web/static/src/js/fields/basic_fields.js +++ b/addons/web/static/src/js/fields/basic_fields.js @@ -2536,6 +2536,7 @@ var JournalDashboardGraph = AbstractField.extend({ * Called when the field is detached from the DOM. */ on_detach_callback: function () { + this.chart.tooltip.hidden(true); this._isInDOM = false; }, 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 1e6bb9af0f0..46b89d2a859 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -795,6 +795,7 @@ var BasicModel = AbstractModel.extend({ }); _.each(fields, function (field) { var dataPoint; + record.data[field.name] = null; if (field.type === 'many2one') { if (field.value) { var id = _.isArray(field.value) ? field.value[0] : field.value; diff --git a/addons/web/static/src/js/views/list/list_renderer.js b/addons/web/static/src/js/views/list/list_renderer.js index ee081aa3c5b..a0c7b9c1836 100644 --- a/addons/web/static/src/js/views/list/list_renderer.js +++ b/addons/web/static/src/js/views/list/list_renderer.js @@ -561,7 +561,7 @@ var ListRenderer = BasicRenderer.extend({ } if (field.type === 'float' || field.type === 'integer' || field.type === 'monetary') { - $th.css({ textAlign: 'right' }); + $th.addClass('o_list_number_th'); } if (config.debug) { diff --git a/addons/web/static/src/scss/list_view.scss b/addons/web/static/src/scss/list_view.scss index 98baf231966..f103d2923fc 100644 --- a/addons/web/static/src/scss/list_view.scss +++ b/addons/web/static/src/scss/list_view.scss @@ -31,6 +31,9 @@ &:not(.o_column_sortable):hover { cursor: default; } + &.o_list_number_th { + text-align: right; + } } } diff --git a/addons/website/models/ir_ui_view.py b/addons/website/models/ir_ui_view.py index 946ed9fe7f8..e81b6ebaf35 100644 --- a/addons/website/models/ir_ui_view.py +++ b/addons/website/models/ir_ui_view.py @@ -133,12 +133,8 @@ class View(models.Model): ('website_id', '!=', None), ]) for specific_parent_view in specific_parent_views: - record.copy({ - # Set key to avoid copy() to generate an unique key as - # we want the specific view to have the same key - 'key': record.key, + record.with_context(website_id=specific_parent_view.website_id.id).write({ 'inherit_id': specific_parent_view.id, - 'website_id': specific_parent_view.website_id.id, }) return records diff --git a/addons/website/models/website_menu.py b/addons/website/models/website_menu.py index 202f6e19039..4afb9bd63e5 100644 --- a/addons/website/models/website_menu.py +++ b/addons/website/models/website_menu.py @@ -149,7 +149,12 @@ class Menu(models.Model): if menu_id.page_id: menu_id.page_id = None else: - page = self.env['website.page'].search(['|', ('url', '=', menu['url']), ('url', '=', '/' + menu['url'])], limit=1) + domain = self.env["website"].website_domain(website_id) + [ + "|", + ("url", "=", menu["url"]), + ("url", "=", "/" + menu["url"]), + ] + page = self.env["website.page"].search(domain, limit=1) if page: menu['page_id'] = page.id menu['url'] = page.url diff --git a/addons/website/static/src/js/widgets/theme.js b/addons/website/static/src/js/widgets/theme.js index a5e0731e4b0..6d774b65073 100644 --- a/addons/website/static/src/js/widgets/theme.js +++ b/addons/website/static/src/js/widgets/theme.js @@ -529,6 +529,34 @@ var ThemeCustomizeDialog = Dialog.extend({ _setActive: function () { var self = this; + // First enforce that all input groups have only one element checked as + // it is supposed to be (it might not be the case on initialization, for + // exemple if we had data-xmlid="A" and data-xmlid="A,B" and if A and B + // are active, the 2 related inputs would be checked). + var $radioXMLInputs = this.$inputs.filter('[type="radio"][data-xmlid]'); + var optionNames = _.uniq(_.map($radioXMLInputs, function (option) { + return option.name; + })); + _.each(optionNames, function (optionName) { + var $inputs = $radioXMLInputs.filter('[name="' + optionName + '"]:checked'); + if ($inputs.length > 1) { + $inputs.prop('checked', false); + + var maxNbXMLIDs = -1; + var $maxInput = null; + _.each($inputs, function (input) { + var $input = $(input); + var xmlID = $input.data('xmlid'); + var nbXMLIDs = xmlID ? xmlID.split(',').length : 0; + if (nbXMLIDs >= maxNbXMLIDs) { + maxNbXMLIDs = nbXMLIDs; + $maxInput = $input; + } + }); + $maxInput.prop('checked', true); + } + }); + // Look at all options to see if they are enabled or disabled var $enable = this.$inputs.filter(':checked'); diff --git a/addons/website/static/src/scss/website.scss b/addons/website/static/src/scss/website.scss index 2aec4f03251..5373d9ec304 100644 --- a/addons/website/static/src/scss/website.scss +++ b/addons/website/static/src/scss/website.scss @@ -534,8 +534,8 @@ table.table_desc tr td { // Default &.s_carousel_default { // Controls - chevron - .carousel-control-prev-icon:before { content: "\f053"; margin-left: 1.5rem; } - .carousel-control-next-icon:before { content: "\f054"; margin-right: 1.5rem; } + .carousel-control-prev-icon:before { content: "\f053" #{"/*rtl:'\f054'*/"}; margin-left: 1.5rem; } + .carousel-control-next-icon:before { content: "\f054" #{"/*rtl:'\f053'*/"}; margin-right: 1.5rem; } .carousel-control-prev-icon:before, .carousel-control-next-icon:before { background-color: rgba(0,0,0,0); diff --git a/addons/website/tests/test_views.py b/addons/website/tests/test_views.py index b8262bb11b8..38b3311ecbd 100644 --- a/addons/website/tests/test_views.py +++ b/addons/website/tests/test_views.py @@ -811,6 +811,43 @@ class TestCowViewSaving(common.TransactionCase): all_title_updated = specific_view.website_meta_title == self.base_view.website_meta_title == "A bug got fixed by updating this field" self.assertEqual(all_title_updated, True, "Update on top level generic views should also be applied on specific views") + def test_module_new_inherit_view_on_parent_already_forked_xpath_replace(self): + """ Deeper, more specific test of above behavior. + A module install should add/update the COW view (if allowed fields, + eg not modified or prohibited (website_id, inherit_id..)). + This test ensure it does not crash if the child view is a primary view. + """ + View = self.env['ir.ui.view'] + + # Simulate layout views + base_view = View.create({ + 'name': 'Main Frontend Layout', + 'type': 'qweb', + 'arch': '<t t-call="web.layout"><t t-set="head_website"/></t>', + 'key': '_portal.frontend_layout', + }).with_context(load_all_views=True) + + inherit_view = View.create({ + 'name': 'Main layout', + 'mode': 'extension', + 'inherit_id': base_view.id, + 'arch': '<xpath expr="//t[@t-set=\'head_website\']" position="replace"><t t-call-assets="web_editor.summernote" t-js="false" groups="website.group_website_publisher"/></xpath>', + 'key': '_website.layout', + }) + + # Trigger cow on website_sale hierarchy for website 1 + base_view.with_context(website_id=1).write({'name': 'Main Frontend Layout (W1)'}) + + # Simulate website_sale_comparison install, that's the real test, it + # should not crash. + View._load_records([dict(xml_id='_website_forum.layout', values={ + 'name': 'Forum Layout', + 'mode': 'primary', + 'inherit_id': inherit_view.id, + 'arch': '<xpath expr="//t[@t-call-assets=\'web_editor.summernote\'][@t-js=\'false\']" position="attributes"><attribute name="groups"/></xpath>', + 'key': '_website_forum.layout', + })]) + def test_multiple_inherit_level(self): """ Test multi-level inheritance: Base diff --git a/addons/website_crm_partner_assign/views/website_crm_partner_assign_templates.xml b/addons/website_crm_partner_assign/views/website_crm_partner_assign_templates.xml index 9e060b8d04e..b5c91d533e4 100644 --- a/addons/website_crm_partner_assign/views/website_crm_partner_assign_templates.xml +++ b/addons/website_crm_partner_assign/views/website_crm_partner_assign_templates.xml @@ -283,7 +283,7 @@ </div> <div class="form-group"> <label class="col-form-label hdd4" for="description">Description</label> - <textarea row="3" name="description" class="form-control description"></textarea> + <textarea rows="3" name="description" class="form-control description"></textarea> </div> </main> <footer class="modal-footer"> @@ -690,7 +690,7 @@ </div> <div class="form-group"> <label class="col-form-label" for="activity_summary">Details Next Activity</label> - <textarea row="3" name="activity_summary" class="form-control activity_summary"><t t-esc="user_activity.summary"/></textarea> + <textarea rows="3" name="activity_summary" class="form-control activity_summary"><t t-esc="user_activity.summary"/></textarea> </div> <div class="form-group"> <label class="col-form-label" for="activity_date_deadline">Next Activity Date</label> diff --git a/addons/website_sale/tests/test_sale_process.py b/addons/website_sale/tests/test_sale_process.py index a0fc55c4106..9cf6f5c5613 100644 --- a/addons/website_sale/tests/test_sale_process.py +++ b/addons/website_sale/tests/test_sale_process.py @@ -130,3 +130,19 @@ class TestWebsiteSaleCheckoutAddress(odoo.tests.TransactionCase): self.default_address_values['partner_id'] = new_partner.id self.WebsiteSaleController.address(**self.default_address_values) self.assertEqual(new_partner.company_id, self.website.company_id, "Public user edited billing (the partner itself) should not get its company modified.") + + def test_04_apply_empty_pl(self): + ''' Ensure empty pl code reset the applied pl ''' + so = self._create_so(self.env.user.partner_id.id) + eur_pl = self.env['product.pricelist'].create({ + 'name': 'EUR_test', + 'website_id': self.website.id, + 'code': 'EUR_test', + }) + + with MockRequest(self.env, website=self.website, sale_order_id=so.id): + self.WebsiteSaleController.pricelist('EUR_test') + self.assertEqual(so.pricelist_id, eur_pl, "Ensure EUR_test is applied") + + self.WebsiteSaleController.pricelist('') + self.assertNotEqual(so.pricelist_id, eur_pl, "Pricelist should be removed when sending an empty pl code") diff --git a/addons/website_sale_wishlist/models/product_wishlist.py b/addons/website_sale_wishlist/models/product_wishlist.py index 1bcbe57baef..0a67caa597c 100644 --- a/addons/website_sale_wishlist/models/product_wishlist.py +++ b/addons/website_sale_wishlist/models/product_wishlist.py @@ -33,7 +33,7 @@ class ProductWishlist(models.Model): else: wish = self.search([("partner_id", "=", self.env.user.partner_id.id)]) - return wish.filtered('product_id.product_tmpl_id.website_published') + return wish.sudo().filtered('product_id.product_tmpl_id.website_published') @api.model def _add_to_wishlist(self, pricelist_id, currency_id, website_id, price, product_id, partner_id=False): diff --git a/doc/cla/individual/Tatider.md b/doc/cla/individual/Tatider.md new file mode 100644 index 00000000000..b2468a491de --- /dev/null +++ b/doc/cla/individual/Tatider.md @@ -0,0 +1,11 @@ +Finland, 2019-07-31 + +I hereby agree to the terms of the Odoo Individual Contributor License +Agreement v1.0. + +I declare that I am authorized and able to make this agreement and sign this +declaration. + +Signed, + +Tatiana Deribina t.i.deribina@gmail.com https://github.com/Tatider diff --git a/doc/cla/individual/anhvu-sg.md b/doc/cla/individual/anhvu-sg.md new file mode 100644 index 00000000000..125880cbcd7 --- /dev/null +++ b/doc/cla/individual/anhvu-sg.md @@ -0,0 +1,11 @@ +Vietnam, 2019-07-27 + +I hereby agree to the terms of the Odoo Individual Contributor License +Agreement v1.0. + +I declare that I am authorized and able to make this agreement and sign this +declaration. + +Signed, + +Anh Vu <vuna2004@gmail.com> https://github.com/anhvu-sg diff --git a/doc/cla/individual/karol1985.md b/doc/cla/individual/karol1985.md new file mode 100644 index 00000000000..b595b686368 --- /dev/null +++ b/doc/cla/individual/karol1985.md @@ -0,0 +1,11 @@ +Poland, 2019-07-31 + +I hereby agree to the terms of the Odoo Individual Contributor License +Agreement v1.0. + +I declare that I am authorized and able to make this agreement and sign this +declaration. + +Signed, + +Karol Rybak karolrybak85@gmail.com https://github.com/karol1985 diff --git a/doc/cla/individual/stfnndrsn.md b/doc/cla/individual/stfnndrsn.md new file mode 100644 index 00000000000..021aec0aca8 --- /dev/null +++ b/doc/cla/individual/stfnndrsn.md @@ -0,0 +1,11 @@ +Denmark, 2019-07-24 + + I hereby agree to the terms of the Odoo Individual Contributor License +Agreement v1.0. + + I declare that I am authorized and able to make this agreement and sign this +declaration. + + Signed, + + Stefan Andersen <stfnandersen@gmail.com> https://github.com/stfnndrsn diff --git a/odoo/addons/base/models/ir_ui_view.py b/odoo/addons/base/models/ir_ui_view.py index bee6fb5206f..c77d91e0eb3 100644 --- a/odoo/addons/base/models/ir_ui_view.py +++ b/odoo/addons/base/models/ir_ui_view.py @@ -646,9 +646,19 @@ actual arch. if node.getparent() is None: source = copy.deepcopy(spec[0]) else: + replaced_node_tag = None for child in spec: if child.get('position') == 'move': child = extract(child) + + if self._context.get('inherit_branding') and not replaced_node_tag and child.tag is not etree.Comment: + # To make a correct branding, we need to + # - know exactly which node has been replaced + # - store it before anything else has altered the Tree + # Do it exactly here :D + child.set('meta-oe-xpath-replacing', node.tag) + replaced_node_tag = node.tag # We just store the replaced node tag on the first child of the xpath replacing it + node.addprevious(child) node.getparent().remove(node) elif pos == 'attributes': @@ -1245,6 +1255,11 @@ actual arch. if child.get('data-oe-xpath'): # injected by view inheritance, skip otherwise # generated xpath is incorrect + # Also, if a node is known to have been replaced during applying xpath + # increment its index to compute an accurate xpath for susequent nodes + replaced_node_tag = child.attrib.pop('meta-oe-xpath-replacing', None) + if replaced_node_tag: + indexes[replaced_node_tag] += 1 self.distribute_branding(child) else: indexes[child.tag] += 1 diff --git a/odoo/addons/base/rng/gantt_view.rng b/odoo/addons/base/rng/gantt_view.rng index 1b0b2b03f63..993b4cfdb4d 100644 --- a/odoo/addons/base/rng/gantt_view.rng +++ b/odoo/addons/base/rng/gantt_view.rng @@ -57,7 +57,7 @@ </rng:optional> </rng:interleave> - <rng:empty/> + <rng:empty/> </rng:element> </rng:define> <rng:start> diff --git a/odoo/addons/base/tests/test_views.py b/odoo/addons/base/tests/test_views.py index cb16873b1a3..458ace7da24 100644 --- a/odoo/addons/base/tests/test_views.py +++ b/odoo/addons/base/tests/test_views.py @@ -674,6 +674,115 @@ class TestTemplating(ViewCase): second.get('data-oe-id'), "second should come from the extension view") + def test_branding_inherit_replace_node(self): + view1 = self.View.create({ + 'name': "Base view", + 'type': 'qweb', + 'arch': """<hello> + <world></world> + <world><t t-esc="hello"/></world> + <world></world> + </hello> + """ + }) + self.View.create({ + 'name': "Extension", + 'type': 'qweb', + 'inherit_id': view1.id, + 'arch': """<xpath expr="/hello/world[1]" position="replace"> + <world>Is a ghetto</world> + <world>Wonder when I'll find paradise</world> + </xpath> + """ + }) + + arch_string = view1.with_context(inherit_branding=True).read_combined(['arch'])['arch'] + + arch = etree.fromstring(arch_string) + self.View.distribute_branding(arch) + + # First world - has been replaced by inheritance + [initial] = arch.xpath('/hello[1]/world[1]') + self.assertEqual( + '/xpath/world[1]', + initial.get('data-oe-xpath'), + 'Inherited nodes have correct xpath') + + # Second world added by inheritance + [initial] = arch.xpath('/hello[1]/world[2]') + self.assertEqual( + '/xpath/world[2]', + initial.get('data-oe-xpath'), + 'Inherited nodes have correct xpath') + + # Third world - is not editable + [initial] = arch.xpath('/hello[1]/world[3]') + self.assertFalse( + initial.get('data-oe-xpath'), + 'node containing t-esc is not branded') + + # The most important assert + # Fourth world - should have a correct oe-xpath, which is 3rd in main view + [initial] = arch.xpath('/hello[1]/world[4]') + self.assertEqual( + '/hello[1]/world[3]', + initial.get('data-oe-xpath'), + "The node's xpath position should be correct") + + def test_branding_inherit_replace_node2(self): + view1 = self.View.create({ + 'name': "Base view", + 'type': 'qweb', + 'arch': """<hello> + <world></world> + <world><t t-esc="hello"/></world> + <world></world> + </hello> + """ + }) + self.View.create({ + 'name': "Extension", + 'type': 'qweb', + 'inherit_id': view1.id, + 'arch': """<xpath expr="/hello/world[1]" position="replace"> + <war>Is a ghetto</war> + <world>Wonder when I'll find paradise</world> + </xpath> + """ + }) + + arch_string = view1.with_context(inherit_branding=True).read_combined(['arch'])['arch'] + + arch = etree.fromstring(arch_string) + self.View.distribute_branding(arch) + + [initial] = arch.xpath('/hello[1]/war[1]') + self.assertEqual( + '/xpath/war', + initial.get('data-oe-xpath'), + 'Inherited nodes have correct xpath') + + # First world: from inheritance + [initial] = arch.xpath('/hello[1]/world[1]') + self.assertEqual( + '/xpath/world', + initial.get('data-oe-xpath'), + 'Inherited nodes have correct xpath') + + # Second world - is not editable + [initial] = arch.xpath('/hello[1]/world[2]') + self.assertFalse( + initial.get('data-oe-xpath'), + 'node containing t-esc is not branded') + + # The most important assert + # Third world - should have a correct oe-xpath, which is 3rd in main view + [initial] = arch.xpath('/hello[1]/world[3]') + self.assertEqual( + '/hello[1]/world[3]', + initial.get('data-oe-xpath'), + "The node's xpath position should be correct") + def test_branding_primary_inherit(self): view1 = self.View.create({ 'name': "Base view", diff --git a/odoo/addons/base/views/res_company_views.xml b/odoo/addons/base/views/res_company_views.xml index 8a0773e98da..6baedeaa03d 100644 --- a/odoo/addons/base/views/res_company_views.xml +++ b/odoo/addons/base/views/res_company_views.xml @@ -30,7 +30,7 @@ </div> </group> <group> - <field name="website" widget="url" placeholder="e.g. www.odoo.com"/> + <field name="website" widget="url" placeholder="e.g. https://www.odoo.com"/> <field name="phone"/> <field name="email"/> <field name="vat"/> diff --git a/odoo/addons/base/views/res_partner_views.xml b/odoo/addons/base/views/res_partner_views.xml index aca476cb0e9..ddd32b7fa50 100644 --- a/odoo/addons/base/views/res_partner_views.xml +++ b/odoo/addons/base/views/res_partner_views.xml @@ -117,7 +117,7 @@ <field name="zip" placeholder="ZIP" class="o_address_zip"/> <field name="country_id" placeholder="Country" class="o_address_country" options='{"no_open": True, "no_create": True}'/> </div> - <field name="website" widget="url" placeholder="e.g. www.odoo.com"/> + <field name="website" widget="url" placeholder="e.g. https://www.odoo.com"/> </group> <group> </group> @@ -260,7 +260,7 @@ <field name="mobile" widget="phone"/> <field name="user_ids" invisible="1"/> <field name="email" widget="email" context="{'gravatar_image': True}" attrs="{'required': [('user_ids','!=', [])]}"/> - <field name="website" widget="url" placeholder="e.g. www.odoo.com"/> + <field name="website" widget="url" placeholder="e.g. https://www.odoo.com"/> <field name="title" options='{"no_open": True}' attrs="{'invisible': [('is_company', '=', True)]}"/> <field name="lang"/> |
