summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2019-08-06 18:29:20 +0200
committerChristophe Simonis <chs@odoo.com>2019-08-06 18:48:24 +0200
commit7d04dc1c7c51dfd18951912628b14bee446a2b01 (patch)
treee89601e493bea092239df55a5ef406b2e05d7d7b /addons
parentb091014686d86ad793e2d1cbf0e7ffea8fbde361 (diff)
parent9e74730a26b09fa4e516bc0d76550f7c7c502fac (diff)
[MERGE] forward port branch saas-12.2 up to 9e74730a26b
Diffstat (limited to 'addons')
-rw-r--r--addons/account/models/account_invoice.py5
-rw-r--r--addons/account/report/account_invoice_report.py5
-rw-r--r--addons/account/static/src/js/reconciliation/reconciliation_model.js2
-rw-r--r--addons/account/static/src/js/reconciliation/reconciliation_renderer.js1
-rw-r--r--addons/account/static/tests/reconciliation_tests.js4
-rw-r--r--addons/account/wizard/account_invoice_send_views.xml1
-rw-r--r--addons/account_analytic_default/models/account_analytic_default.py5
-rw-r--r--addons/account_voucher/models/account_voucher.py5
-rw-r--r--addons/crm/views/crm_lead_views.xml4
-rw-r--r--addons/digest/views/digest_templates.xml2
-rw-r--r--addons/google_drive/models/res_config_settings.py10
-rw-r--r--addons/hr/security/hr_security.xml7
-rw-r--r--addons/hr_holidays/models/hr_leave.py2
-rw-r--r--addons/hr_holidays/models/hr_leave_allocation.py3
-rw-r--r--addons/hr_timesheet/views/res_config_settings_views.xml2
-rw-r--r--addons/hr_timesheet_attendance/report/hr_timesheet_attendance_report.py4
-rw-r--r--addons/l10n_es/data/account.account.template-common.csv2
-rw-r--r--addons/l10n_es/data/account_tax_data.xml14
-rw-r--r--addons/l10n_mn/__manifest__.py1
-rw-r--r--addons/l10n_mx/data/account.account.template.csv2
-rw-r--r--addons/lunch/security/lunch_security.xml20
-rw-r--r--addons/mail/models/mail_activity.py13
-rw-r--r--addons/mail/models/mail_thread.py2
-rw-r--r--addons/mail/static/src/js/emojis.js1
-rw-r--r--addons/mail/static/src/js/services/mail_notification_manager.js2
-rw-r--r--addons/mail/static/src/scss/composer.scss2
-rw-r--r--addons/mail/views/mail_templates.xml2
-rw-r--r--addons/mail/wizard/mail_compose_message.py1
-rw-r--r--addons/mass_mailing/static/src/scss/themes/theme_basic.scss29
-rw-r--r--addons/mass_mailing/static/src/scss/themes/theme_default.scss17
-rw-r--r--addons/mrp/models/mrp_production.py2
-rw-r--r--addons/mrp/wizard/change_production_qty.py3
-rw-r--r--addons/partner_autocomplete/models/res_partner.py2
-rw-r--r--addons/point_of_sale/models/pos_order.py14
-rw-r--r--addons/point_of_sale/static/src/js/models.js33
-rw-r--r--addons/point_of_sale/views/pos_order_view.xml1
-rw-r--r--addons/portal/models/mail_message.py5
-rw-r--r--addons/portal/static/src/xml/portal_chatter.xml2
-rw-r--r--addons/portal/views/portal_templates.xml2
-rw-r--r--addons/project/views/project_views.xml1
-rw-r--r--addons/purchase_stock/models/stock_rule.py2
-rw-r--r--addons/sale/models/sale.py3
-rw-r--r--addons/sale/tests/test_sale_order.py53
-rw-r--r--addons/sale/views/sale_portal_templates.xml3
-rw-r--r--addons/sale_purchase/models/sale_order.py4
-rw-r--r--addons/stock/models/res_config_settings.py10
-rw-r--r--addons/stock/security/stock_security.xml2
-rw-r--r--addons/stock/views/product_views.xml2
-rw-r--r--addons/stock_dropshipping/__manifest__.py2
-rw-r--r--addons/stock_dropshipping/tests/test_dropship.py2
-rw-r--r--addons/stock_dropshipping/views/sale_order_views.xml24
-rw-r--r--addons/test_mail/tests/test_performance.py2
-rw-r--r--addons/web/controllers/main.py4
-rw-r--r--addons/web/static/src/js/fields/basic_fields.js1
-rw-r--r--addons/web/static/src/js/views/basic/basic_model.js1
-rw-r--r--addons/web/static/src/js/views/calendar/calendar_controller.js7
-rw-r--r--addons/web/static/src/js/views/list/list_renderer.js2
-rw-r--r--addons/web/static/src/scss/list_view.scss3
-rw-r--r--addons/web/static/tests/views/calendar_tests.js58
-rw-r--r--addons/website/models/ir_ui_view.py6
-rw-r--r--addons/website/models/website_menu.py7
-rw-r--r--addons/website/static/src/js/widgets/theme.js28
-rw-r--r--addons/website/static/src/scss/website.scss4
-rw-r--r--addons/website/tests/test_views.py37
-rw-r--r--addons/website_crm_partner_assign/views/website_crm_partner_assign_templates.xml4
-rw-r--r--addons/website_forum/static/src/js/website_forum.js33
-rw-r--r--addons/website_forum/static/src/scss/website_forum.scss29
-rw-r--r--addons/website_forum/views/website_forum.xml5
-rw-r--r--addons/website_sale/tests/test_sale_process.py16
-rw-r--r--addons/website_sale_wishlist/models/product_wishlist.py2
70 files changed, 496 insertions, 95 deletions
diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py
index 28f508ea1c0..b82b8756a1c 100644
--- a/addons/account/models/account_invoice.py
+++ b/addons/account/models/account_invoice.py
@@ -2012,7 +2012,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 985bcd15496..f7f29efb011 100644
--- a/addons/account/static/src/js/reconciliation/reconciliation_model.js
+++ b/addons/account/static/src/js/reconciliation/reconciliation_model.js
@@ -932,7 +932,7 @@ var StatementModel = BasicModel.extend({
'link': prop.id,
'tax_ids': [tax.id],
'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 e6be300714d..7103e3f7d71 100644
--- a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js
+++ b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js
@@ -794,6 +794,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 d2f52cb382d..e565ec91ce2 100644
--- a/addons/account/static/tests/reconciliation_tests.js
+++ b/addons/account/static/tests/reconciliation_tests.js
@@ -1299,14 +1299,14 @@ QUnit.module('account', {
await testUtils.dom.click(widget.$('.create .quick_add button:contains(ATOS)'));
assert.strictEqual(widget.$('.accounting_view tbody .cell_label, .accounting_view tbody .cell_right').text().replace(/[\n\r\s$,]+/g, ' '),
- " ATOS Banque 1145.62 ATOS Frais 26.71 Tax 20.00% 229.12 Tax 10.00% include 2.67 ", "should display 4 lines");
+ " ATOS Banque 1145.62 ATOS Frais 26.71 ATOS Banque Tax 20.00% 229.12 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");
await testUtils.fields.editAndTrigger(widget.$('.create .create_amount input'), '100',['input']);
assert.strictEqual(widget.$('.accounting_view tbody').text().replace(/[\n\r\s$,]+/g, ' '),
- " 101120 New ATOS Banque 1075.00 101130 New ATOS Frais 90.91 101120 New Tax 20.00% 215.00 101300 New Tax 10.00% include 9.09 ",
+ " 101120 New ATOS Banque 1075.00 101130 New ATOS Frais 90.91 101120 New ATOS Banque Tax 20.00% 215.00 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 e6857e7f4ad..cf2480aa1f5 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"/>
@@ -604,7 +604,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 8d435e67463..30958ef43d1 100644
--- a/addons/hr/security/hr_security.xml
+++ b/addons/hr/security/hr_security.xml
@@ -47,5 +47,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 97bf33abc7f..a9bc53d1bae 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_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 91bf5658093..50f71e713a0 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 9461c865a72..dca71560db1 100644
--- a/addons/l10n_es/data/account_tax_data.xml
+++ b/addons/l10n_es/data/account_tax_data.xml
@@ -445,6 +445,7 @@
<field name="description">P_IVA21_SP_IN</field>
<field name="type_tax_use">purchase</field>
<field name="chart_template_id" ref="l10n_es.account_chart_template_common"/>
+ <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="amount_type">percent</field>
<field name="amount" eval="21"/>
<field name="tax_group_id" ref="tax_group_iva_21"/>
@@ -550,6 +551,7 @@
<field name="type_tax_use">purchase</field>
<field name="amount_type">percent</field>
<field name="amount" eval="21"/>
+ <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"/>
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
@@ -716,6 +718,7 @@
<field name="chart_template_id" ref="l10n_es.account_chart_template_common"/>
<field name="amount_type">percent</field>
<field name="type_tax_use">purchase</field>
+ <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"/>
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
@@ -767,6 +770,7 @@
<field name="description">P_IVA10_SP_EX</field>
<field name="chart_template_id" ref="l10n_es.account_chart_template_common"/>
<field name="amount_type">percent</field>
+ <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"/>
@@ -820,6 +824,7 @@
<field name="type_tax_use">purchase</field>
<field name="amount" eval="21"/>
<field name="chart_template_id" ref="l10n_es.account_chart_template_common"/>
+ <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="amount_type">percent</field>
<field name="tax_group_id" ref="tax_group_iva_21"/>
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
@@ -869,6 +874,7 @@
<field name="amount" eval="4"/>
<field name="description">P_IVA4_IC_BC</field>
<field name="chart_template_id" ref="l10n_es.account_chart_template_common"/>
+ <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="amount_type">percent</field>
<field name="type_tax_use">purchase</field>
<field name="name">IVA 4% Adquisición Intracomunitario. Bienes corrientes</field>
@@ -924,6 +930,7 @@
<field name="amount_type">percent</field>
<field name="type_tax_use">purchase</field>
<field name="name">IVA 4% Adquisición Intracomunitario. Bienes de inversión</field>
+ <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"/>
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
@@ -972,6 +979,7 @@
<field name="amount" eval="10"/>
<field name="description">P_IVA10_IC_BC</field>
<field name="chart_template_id" ref="l10n_es.account_chart_template_common"/>
+ <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="amount_type">percent</field>
<field name="type_tax_use">purchase</field>
<field name="name">IVA 10% Adquisición Intracomunitario. Bienes corrientes</field>
@@ -1026,6 +1034,7 @@
<field name="amount_type">percent</field>
<field name="type_tax_use">purchase</field>
<field name="name">IVA 10% Adquisición Intracomunitario. Bienes de inversión</field>
+ <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"/>
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
@@ -3068,6 +3077,7 @@
<field name="amount_type">percent</field>
<field name="type_tax_use">purchase</field>
<field name="name">IVA 10% Adquisición de servicios intracomunitarios</field>
+ <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"/>
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
@@ -3119,6 +3129,7 @@
<field name="amount_type">percent</field>
<field name="type_tax_use">purchase</field>
<field name="name">IVA 4% Adquisición de servicios intracomunitarios</field>
+ <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"/>
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
@@ -3392,6 +3403,7 @@
<field name="description">P_IVA4_ISP</field>
<field name="name">IVA 4% Compra con Inversión del Sujeto Pasivo Nacional</field>
<field name="type_tax_use">purchase</field>
+ <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="amount_type">percent</field>
<field name="amount" eval="4"/>
<field name="chart_template_id" ref="l10n_es.account_chart_template_common"/>
@@ -3443,6 +3455,7 @@
<field name="description">P_IVA10_ISP</field>
<field name="name">IVA 10% Compra con Inversión del Sujeto Pasivo Nacional</field>
<field name="type_tax_use">purchase</field>
+ <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="amount_type">percent</field>
<field name="amount" eval="10"/>
<field name="chart_template_id" ref="l10n_es.account_chart_template_common"/>
@@ -3496,6 +3509,7 @@
<field name="type_tax_use">purchase</field>
<field name="amount_type">percent</field>
<field name="amount" eval="21"/>
+ <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"/>
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
diff --git a/addons/l10n_mn/__manifest__.py b/addons/l10n_mn/__manifest__.py
index ba60ad4da0f..bd1a8436eb6 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/lunch/security/lunch_security.xml b/addons/lunch/security/lunch_security.xml
index 1da0445b001..ba58056ff3d 100644
--- a/addons/lunch/security/lunch_security.xml
+++ b/addons/lunch/security/lunch_security.xml
@@ -40,6 +40,26 @@
<field name="groups" eval="[(4,ref('lunch.group_lunch_user'))]"/>
</record>
+ <record id="lunch_order_rule_write" model="ir.rule">
+ <field name="name">lunch.order: Don't change confirmed order</field>
+ <field name="model_id" ref="lunch.model_lunch_order"/>
+ <field name="domain_force">[('state', '!=', 'confirmed'), ('user_id', '=', user.id)]</field>
+ <field name="perm_read" eval="0"/>
+ <field name="perm_create" eval="0"/>
+ <field name="perm_unlink" eval="0"/>
+ <field name="groups" eval="[(4, ref('base.group_user'))]"/>
+ </record>
+
+ <record id="lunch_order_rule_write_manager" model="ir.rule">
+ <field name="name">manager can do whatever</field>
+ <field name="model_id" ref="lunch.model_lunch_order"/>
+ <field name="domain_force">[(1, '=', 1)]</field>
+ <field name="perm_read" eval="0"/>
+ <field name="perm_create" eval="0"/>
+ <field name="perm_unlink" eval="0"/>
+ <field name="groups" eval="[(4, ref('lunch.group_lunch_manager'))]"/>
+ </record>
+
<data noupdate="1">
<record id="base.default_user" model="res.users">
<field name="groups_id" eval="[(4,ref('lunch.group_lunch_manager'))]"/>
diff --git a/addons/mail/models/mail_activity.py b/addons/mail/models/mail_activity.py
index bf0b0141604..16e0b261291 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_id.partner_id.id
+ except exceptions.AccessError:
+ need_sudo = True
+ partner_id = activity.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 9ed782b40a8..dd92247078f 100644
--- a/addons/mail/models/mail_thread.py
+++ b/addons/mail/models/mail_thread.py
@@ -431,7 +431,7 @@ class MailThread(models.AbstractModel):
# ------------------------------------------------------
def with_lang(self):
- if 'lang' not in self._context:
+ if not self._context.get("lang"):
return self.with_context(lang=self.env.user.lang)
return self
diff --git a/addons/mail/static/src/js/emojis.js b/addons/mail/static/src/js/emojis.js
index 54b995f3bb6..f8684cde541 100644
--- a/addons/mail/static/src/js/emojis.js
+++ b/addons/mail/static/src/js/emojis.js
@@ -73,6 +73,7 @@ var data = {
"&lt;3": "❤️",
":heart": "❤️", // alt.
"</3": "💔",
+ "&lt;/3": "💔",
":heart_eyes": "😍",
":turban": "👳",
":+1": "👍",
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 c22276867b1..8b9b10b2de5 100644
--- a/addons/mail/static/src/js/services/mail_notification_manager.js
+++ b/addons/mail/static/src/js/services/mail_notification_manager.js
@@ -506,7 +506,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 802236afd73..b1e78345d1f 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 b2417cd83b0..8fb58741a9f 100644
--- a/addons/mrp/models/mrp_production.py
+++ b/addons/mrp/models/mrp_production.py
@@ -205,7 +205,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 bcf2d0aa461..ec31b842a7f 100644
--- a/addons/mrp/wizard/change_production_qty.py
+++ b/addons/mrp/wizard/change_production_qty.py
@@ -62,7 +62,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 b4411b1430b..cdd15360bb8 100644
--- a/addons/point_of_sale/models/pos_order.py
+++ b/addons/point_of_sale/models/pos_order.py
@@ -335,7 +335,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."""
@@ -484,6 +484,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:
@@ -1162,6 +1173,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 53f3c5f3c25..31a8f88c4af 100644
--- a/addons/point_of_sale/static/src/js/models.js
+++ b/addons/point_of_sale/static/src/js/models.js
@@ -2619,9 +2619,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&amp;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 90d7476546f..6c0fa5a0df8 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 4731e22f335..06d1a37ae40 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 036b285f9fa..10a8eb7d593 100644
--- a/addons/sale/views/sale_portal_templates.xml
+++ b/addons/sale/views/sale_portal_templates.xml
@@ -464,12 +464,11 @@
<div
t-if="line.discount &gt;= 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/sale_purchase/models/sale_order.py b/addons/sale_purchase/models/sale_order.py
index 8960907f9ed..0b85753411f 100644
--- a/addons/sale_purchase/models/sale_order.py
+++ b/addons/sale_purchase/models/sale_order.py
@@ -114,8 +114,8 @@ class SaleOrderLine(models.Model):
decreased_values = {}
if 'product_uom_qty' in values:
precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
- increased_lines = self.sudo().filtered(lambda r: r.purchase_line_count and float_compare(r.product_uom_qty, values['product_uom_qty'], precision_digits=precision) == -1)
- decreased_lines = self.sudo().filtered(lambda r: r.purchase_line_count and float_compare(r.product_uom_qty, values['product_uom_qty'], precision_digits=precision) == 1)
+ increased_lines = self.sudo().filtered(lambda r: r.product_id.service_to_purchase and r.purchase_line_count and float_compare(r.product_uom_qty, values['product_uom_qty'], precision_digits=precision) == -1)
+ decreased_lines = self.sudo().filtered(lambda r: r.product_id.service_to_purchase and r.purchase_line_count and float_compare(r.product_uom_qty, values['product_uom_qty'], precision_digits=precision) == 1)
increased_values = {line.id: line.product_uom_qty for line in increased_lines}
decreased_values = {line.id: line.product_uom_qty for line in decreased_lines}
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 a851beb0cfc..8c915b4969e 100644
--- a/addons/stock/security/stock_security.xml
+++ b/addons/stock/security/stock_security.xml
@@ -99,7 +99,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 c624721d40a..64be1d7a250 100644
--- a/addons/stock/views/product_views.xml
+++ b/addons/stock/views/product_views.xml
@@ -194,6 +194,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>
@@ -285,6 +286,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/tests/test_dropship.py b/addons/stock_dropshipping/tests/test_dropship.py
index f70897f9eb8..9048fb2d009 100644
--- a/addons/stock_dropshipping/tests/test_dropship.py
+++ b/addons/stock_dropshipping/tests/test_dropship.py
@@ -45,7 +45,7 @@ class TestDropship(common.TransactionCase):
self.assertAlmostEqual(po_line.product_qty, 1.00)
# Update qty on SO and check PO
- so.order_line.product_uom_qty = 2.00
+ so.write({'order_line': [[1, so.order_line.id, {'product_uom_qty': 2.00}]]})
self.assertAlmostEqual(po_line.product_qty, 2.00)
# Create a new so line
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', '&gt;', 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', '&gt;', 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 be51e1be5d9..b4420570a1c 100644
--- a/addons/test_mail/tests/test_performance.py
+++ b/addons/test_mail/tests/test_performance.py
@@ -485,7 +485,7 @@ class TestHeavyMailPerformance(TransactionCase):
'user_id': self.env.uid,
})
self.assertEqual(rec.message_partner_ids, self.partners | self.env.user.partner_id)
- with self.assertQueryCount(__system__=60, emp=77): # com runbot: 61 - 76 // test_mail only: 60 - 72
+ with self.assertQueryCount(__system__=61, emp=77): # 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)
# write tracking message
diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py
index 9d95ae485a9..0f0bf9da958 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 b3bf09ab88d..437543ef184 100644
--- a/addons/web/static/src/js/fields/basic_fields.js
+++ b/addons/web/static/src/js/fields/basic_fields.js
@@ -2526,6 +2526,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 ca9c03ce7d6..1a7f0b5f079 100644
--- a/addons/web/static/src/js/views/basic/basic_model.js
+++ b/addons/web/static/src/js/views/basic/basic_model.js
@@ -796,6 +796,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/calendar/calendar_controller.js b/addons/web/static/src/js/views/calendar/calendar_controller.js
index b3a04ec9a2b..bbfcb02def5 100644
--- a/addons/web/static/src/js/views/calendar/calendar_controller.js
+++ b/addons/web/static/src/js/views/calendar/calendar_controller.js
@@ -55,6 +55,7 @@ var CalendarController = AbstractController.extend({
this.readonlyFormViewId = params.readonlyFormViewId;
this.mapping = params.mapping;
this.context = params.context;
+ this.previousOpen = null;
// The quickCreating attribute ensures that we don't do several create
this.quickCreating = false;
},
@@ -271,7 +272,8 @@ var CalendarController = AbstractController.extend({
title += ': ' + this.renderer.arch.attrs.string;
}
if (this.eventOpenPopup) {
- new dialogs.FormViewDialog(self, {
+ if (this.previousOpen) { this.previousOpen.close(); }
+ this.previousOpen = new dialogs.FormViewDialog(self, {
res_model: this.modelName,
context: context,
title: title,
@@ -283,7 +285,8 @@ var CalendarController = AbstractController.extend({
}
self.reload();
},
- }).open();
+ });
+ this.previousOpen.open();
} else {
this.do_action({
type: 'ir.actions.act_window',
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 aed795a6b9b..ed5af4c96bb 100644
--- a/addons/web/static/src/js/views/list/list_renderer.js
+++ b/addons/web/static/src/js/views/list/list_renderer.js
@@ -708,7 +708,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 9ccf32cde2e..ab3707e6ddf 100644
--- a/addons/web/static/src/scss/list_view.scss
+++ b/addons/web/static/src/scss/list_view.scss
@@ -19,6 +19,9 @@
&:not(.o_column_sortable):hover {
cursor: default;
}
+ &.o_list_number_th {
+ text-align: right;
+ }
}
}
tbody > tr > td:not(.o_list_record_selector) {
diff --git a/addons/web/static/tests/views/calendar_tests.js b/addons/web/static/tests/views/calendar_tests.js
index e217e47f3d1..d8a34561d87 100644
--- a/addons/web/static/tests/views/calendar_tests.js
+++ b/addons/web/static/tests/views/calendar_tests.js
@@ -5,6 +5,7 @@ var AbstractStorageService = require('web.AbstractStorageService');
var CalendarView = require('web.CalendarView');
var CalendarRenderer = require('web.CalendarRenderer');
var Dialog = require('web.Dialog');
+var ViewDialogs = require('web.view_dialogs');
var fieldUtils = require('web.field_utils');
var mixins = require('web.mixins');
var RamStorage = require('web.RamStorage');
@@ -475,6 +476,63 @@ QUnit.module('Views', {
calendar.destroy();
});
+ QUnit.test('open multiple event form at the same time', async function (assert) {
+ assert.expect(2);
+
+ var prom = testUtils.makeTestPromise();
+ var counter = 0;
+ testUtils.mock.patch(ViewDialogs.FormViewDialog, {
+ open: function () {
+ counter++;
+ this.options = _.omit(this.options, 'fields_view'); // force loadFieldView
+ return this._super.apply(this, arguments);
+ },
+ loadFieldView: function () {
+ var self = this;
+ var args = arguments;
+ var _super = this._super;
+ return prom.then(function () {
+ return _super.apply(self, args);
+ });
+ },
+ });
+
+ var event = $.Event();
+ var calendar = await createCalendarView({
+ View: CalendarView,
+ model: 'event',
+ data: this.data,
+ arch:
+ '<calendar class="o_calendar_test" '+
+ 'string="Events" ' +
+ 'event_open_popup="true" '+
+ 'date_start="start" '+
+ 'quick_add="False" '+
+ 'date_stop="stop" '+
+ 'all_day="allday" '+
+ 'mode="month" '+
+ 'readonly_form_view_id="1">'+
+ '<field name="name"/>'+
+ '</calendar>',
+ archs: archs,
+ viewOptions: {
+ initialDate: initialDate,
+ },
+ });
+
+ var $cell = calendar.$('.fc-day-grid .fc-row:eq(2) .fc-day:eq(2)');
+ for (var i = 0; i < 5; i++) {
+ await testUtils.dom.triggerMouseEvent($cell, "mousedown");
+ await testUtils.dom.triggerMouseEvent($cell, "mouseup");
+ }
+ prom.resolve();
+ await testUtils.nextTick();
+ assert.equal(counter, 5, "there should had been 5 attemps to open a modal");
+ assert.containsOnce($('body'), '.modal', "there should be only one open modal");
+
+ calendar.destroy();
+ });
+
QUnit.test('create event with timezone in week mode European locale', async function (assert) {
assert.expect(5);
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 352c79b6427..01befb7c925 100644
--- a/addons/website/static/src/js/widgets/theme.js
+++ b/addons/website/static/src/js/widgets/theme.js
@@ -532,6 +532,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 e384e8df34b..661aa693227 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 d67c0fb5edc..fe2837ec080 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">
@@ -691,7 +691,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_forum/static/src/js/website_forum.js b/addons/website_forum/static/src/js/website_forum.js
index 88dd7e222a7..832d29936ab 100644
--- a/addons/website_forum/static/src/js/website_forum.js
+++ b/addons/website_forum/static/src/js/website_forum.js
@@ -24,6 +24,9 @@ publicWidget.registry.websiteForum = publicWidget.Widget.extend({
'click .vote_up:not(.karma_required), .vote_down:not(.karma_required)': '_onVotePostClick',
'click .o_js_validation_queue a[href*="/validate"]': '_onValidationQueueClick',
'click .accept_answer:not(.karma_required)': '_onAcceptAnswerClick',
+ 'click .validate_answer [data-href]': '_onAcceptAnswerClick',
+ 'mouseenter .validate_answer [data-href]': '_onRemoveValidAnswerMouse',
+ 'mouseleave .validate_answer [data-href]': '_onRemoveValidAnswerMouse',
'click .favourite_question': '_onFavoriteQuestionClick',
'click .comment_delete': '_onDeleteCommentClick',
'click .notification_close': '_onCloseNotificationClick',
@@ -350,6 +353,8 @@ publicWidget.registry.websiteForum = publicWidget.Widget.extend({
*/
_onAcceptAnswerClick: function (ev) {
ev.preventDefault();
+ var self = this;
+ var $acceptAnswerLinks = this.$('.accept_answer');
var $link = $(ev.currentTarget);
this._rpc({
route: $link.data('href'),
@@ -368,19 +373,41 @@ publicWidget.registry.websiteForum = publicWidget.Widget.extend({
$link.parent().append($warning);
}
} else {
+ $acceptAnswerLinks.removeClass('oe_answer_true')
+ .addClass('oe_answer_false');
$link.toggleClass('oe_answer_true', !!data)
.toggleClass('oe_answer_false', !data);
+
+ // TODO in master, review the utility of this function...
+ self._onCheckAnswerStatus(ev);
+
+ // If we are removing an accepted answer, reload the page as the
+ // design is quite different with or without an accepted answer.
+ if ($link.closest('.validate_answer').length) {
+ window.location.reload();
+ }
}
});
- this._onCheckAnswerStatus(ev);
+ },
+ /**
+ * @private
+ * @param {Event} ev
+ */
+ _onRemoveValidAnswerMouse: function (ev) {
+ var hover = (ev.type === 'mouseenter');
+ $(ev.currentTarget).find('.fa')
+ .toggleClass('fa-times-circle text-danger', hover)
+ .toggleClass('fa-check-circle text-success', !hover);
},
/**
* @private
* @param {Event} ev
*/
_onCheckAnswerStatus: function (ev) {
- var $link = $(ev.currentTarget);
- $link.toggleClass('text-success', !$link.hasClass('oe_answer_true'));
+ _.each(this.$('.accept_answer'), function (link) {
+ var $link = $(link);
+ $link.toggleClass('text-success', $link.hasClass('oe_answer_true'));
+ });
},
/**
* @private
diff --git a/addons/website_forum/static/src/scss/website_forum.scss b/addons/website_forum/static/src/scss/website_forum.scss
index a5b0a4f1db4..59ed45536ef 100644
--- a/addons/website_forum/static/src/scss/website_forum.scss
+++ b/addons/website_forum/static/src/scss/website_forum.scss
@@ -47,16 +47,23 @@ $bronze: #eea91e;
}
.validate_answer {
- @include o-position-absolute(0,0,0,0);
+ @include o-position-absolute(0, auto, 0, 0);
+ width: 4px;
height: 100%;
- .bg-success {
- @include o-position-absolute(0,0,0,0);
- height: 100%;
- width: 4px;
+ z-index: 1; // On top of card content if an alert is displayed inside
+
+ > .bg-success {
+ @include o-position-absolute(0, 0, 0, 0);
}
- span {
- @include o-position-absolute(10px,0,0,-9px);
+ > span {
+ @include o-position-absolute(10px, 50%);
+ transform: translateX(50%);
font-size: 20px;
+
+ &[data-href] {
+ cursor: pointer;
+ }
+
i {
padding: 2px 3px;
border-radius: 100%;
@@ -85,6 +92,10 @@ $bronze: #eea91e;
.favourite_question {
color: $gray-400 !important;
+
+ &.forum_favourite_question {
+ color: $gold !important;
+ }
}
.oe_grey {
@@ -186,10 +197,6 @@ img.o_forum_avatar_big {
width: auto;
}
-.forum_favourite_question {
- color: $gold !important;
-}
-
.o_forum_user_bio_expand {
position: absolute;
display: none;
diff --git a/addons/website_forum/views/website_forum.xml b/addons/website_forum/views/website_forum.xml
index 45160c618ef..d5c505a8e81 100644
--- a/addons/website_forum/views/website_forum.xml
+++ b/addons/website_forum/views/website_forum.xml
@@ -800,7 +800,10 @@
<div class="card-body">
<div t-if="answer.is_correct" class="validate_answer">
<div class="bg-success"></div>
- <span title="Question has accepted answer" role="img" aria-label="Question has accepted answer"><i class="fa fa-check-circle text-success bg-white"/></span>
+ <span title="Question has accepted answer" role="img" aria-label="Question has accepted answer"
+ t-attf-data-href="/forum/#{slug(question.forum_id)}/post/#{slug(answer)}/toggle_correct">
+ <i class="fa fa-check-circle text-success bg-white"/>
+ </span>
</div>
<div class="row">
<div class="col-1">
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):