summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2019-08-13 10:08:12 +0200
committerChristophe Simonis <chs@odoo.com>2019-08-13 10:08:12 +0200
commitb2872e9bab5b84f7a83548d7ea0e864e4aa0d131 (patch)
tree8cc57082fd3a1b307e76a46a51eca9a66d55e258 /addons
parent2f90b39a6689607f7b812e648dee966bb37c2190 (diff)
parent499d3efa7ce4b7f6756658bff82e4a8b2f57c050 (diff)
[MERGE] forward port branch saas-12.3 up to 499d3efa7ce
Diffstat (limited to 'addons')
-rw-r--r--addons/account/models/account.py9
-rw-r--r--addons/account/models/account_payment.py42
-rw-r--r--addons/account/views/account_payment_view.xml1
-rw-r--r--addons/hr_recruitment/models/hr_job.py9
-rw-r--r--addons/l10n_ae/data/account_tax_template_data.xml1
-rw-r--r--addons/mail/static/src/js/services/mail_window_manager.js2
-rw-r--r--addons/mail/static/tests/thread_window/basic_thread_window_tests.js337
-rw-r--r--addons/point_of_sale/static/src/xml/pos.xml2
-rw-r--r--addons/point_of_sale/views/pos_session_view.xml2
-rw-r--r--addons/project_timesheet_holidays/models/hr_holidays.py1
-rw-r--r--addons/project_timesheet_holidays/models/res_company.py2
-rw-r--r--addons/web/static/src/js/views/control_panel/control_panel_model.js12
-rw-r--r--addons/web/static/src/js/views/control_panel/search/add_new_favorite_menu.js6
-rw-r--r--addons/web/static/src/js/views/control_panel/search/favorite_menu.js12
-rw-r--r--addons/web/static/src/js/widgets/domain_selector.js2
-rw-r--r--addons/web/static/src/js/widgets/model_field_selector.js7
-rw-r--r--addons/web/static/src/xml/base.xml12
-rw-r--r--addons/web/static/tests/views/search_view_tests.js167
-rw-r--r--addons/web/static/tests/widgets/model_field_selector_tests.js40
-rw-r--r--addons/website_profile/static/src/scss/website_profile.scss4
-rw-r--r--addons/website_rating/static/src/scss/website_rating.scss4
-rw-r--r--addons/website_rating/static/src/xml/portal_chatter.xml2
-rw-r--r--addons/website_slides/static/src/scss/slides_slide_fullscreen.scss10
-rw-r--r--addons/website_slides/static/src/scss/website_slides.scss2
-rw-r--r--addons/website_slides_forum/views/website_slides_forum_templates.xml2
25 files changed, 487 insertions, 203 deletions
diff --git a/addons/account/models/account.py b/addons/account/models/account.py
index 940f0a25522..30ab7082504 100644
--- a/addons/account/models/account.py
+++ b/addons/account/models/account.py
@@ -1422,13 +1422,16 @@ class AccountTax(models.Model):
repartition_lines_to_treat = len(tax_repartition_lines)
total_amount = 0
for repartition_line in tax_repartition_lines:
- # In case some rounding error occurs, we compensate for it on the last line
- line_amount = round(sign * tax_amount * repartition_line.factor if repartition_lines_to_treat != 1 else sign * (tax_amount - total_amount), prec)
+ if repartition_lines_to_treat != 1:
+ line_amount = round(tax_amount * repartition_line.factor, prec)
+ else:
+ # In case some rounding error occurs, we compensate for it on the last line
+ line_amount = round(tax_amount - total_amount, prec)
taxes_vals.append({
'id': tax.id,
'name': partner and tax.with_context(lang=partner.lang).name or tax.name,
- 'amount': line_amount,
+ 'amount': sign * line_amount,
'base': round(sign * base, prec),
'sequence': tax.sequence,
'account_id': tax.cash_basis_transition_account_id.id if tax.tax_exigibility == 'on_payment' else repartition_line.account_id.id,
diff --git a/addons/account/models/account_payment.py b/addons/account/models/account_payment.py
index 18528685864..7fbb328c69d 100644
--- a/addons/account/models/account_payment.py
+++ b/addons/account/models/account_payment.py
@@ -3,6 +3,8 @@
from odoo import models, fields, api, _
from odoo.exceptions import UserError, ValidationError
+from collections import defaultdict
+
MAP_INVOICE_TYPE_PARTNER_TYPE = {
'out_invoice': 'customer',
'out_refund': 'customer',
@@ -731,6 +733,7 @@ class payment_register(models.TransientModel):
"Batch Deposit: Encase several customer checks at once by generating a batch deposit to submit to your bank. When encoding the bank statement in Odoo, you are suggested to reconcile the transaction with the batch deposit.To enable batch deposit, module account_batch_payment must be installed.\n"
"SEPA Credit Transfer: Pay bill from a SEPA Credit Transfer file you submit to your bank. To enable sepa credit transfer, module account_sepa must be installed ")
invoice_ids = fields.Many2many('account.move', 'account_invoice_payment_rel_transient', 'payment_id', 'invoice_id', string="Invoices", copy=False, readonly=True)
+ group_payment = fields.Boolean(help="Only one payment will be created by partner (bank)/ currency.")
@api.model
def default_get(self, fields):
@@ -746,6 +749,8 @@ class payment_register(models.TransientModel):
first_outbound = invoices[0].is_outbound()
if any(x != first_outbound for x in outbound_list):
raise UserError(_("You can only register at the same time for payment that are all inbound or all outbound"))
+ if any(inv.company_id != invoices[0].company_id for inv in invoices):
+ raise UserError(_("You can only register at the same time for payment that are all from the same company"))
if 'invoice_ids' not in rec:
rec['invoice_ids'] = [(6, 0, invoices.ids)]
if 'journal_id' not in rec:
@@ -764,33 +769,34 @@ class payment_register(models.TransientModel):
invoices = self.env['account.move'].browse(active_ids)
if self.journal_id and invoices:
if invoices[0].is_inbound():
- domain = [('payment_type', '=', 'inbound'), ('id', 'in', self.journal_id.inbound_payment_method_ids.ids)]
+ domain_payment = [('payment_type', '=', 'inbound'), ('id', 'in', self.journal_id.inbound_payment_method_ids.ids)]
else:
- domain = [('payment_type', '=', 'outbound'), ('id', 'in', self.journal_id.outbound_payment_method_ids.ids)]
-
- return {'domain': {'payment_method_id': domain}}
+ domain_payment = [('payment_type', '=', 'outbound'), ('id', 'in', self.journal_id.outbound_payment_method_ids.ids)]
+ domain_journal = [('type', 'in', ('bank', 'cash')), ('company_id', '=', invoices[0].company_id.id)]
+ return {'domain': {'payment_method_id': domain_payment, 'journal_id': domain_journal}}
return {}
- @api.multi
- def _prepare_payment_vals(self, invoice):
+ def _prepare_payment_vals(self, invoices):
'''Create the payment values.
- :param invoice: A single invoice/bill to pay.
+ :param invoices: The invoices/bills to pay. In case of multiple
+ documents, they need to be grouped by partner, bank, journal and
+ currency.
:return: The payment values as a dictionary.
'''
- amount = self.env['account.payment']._compute_payment_amount(invoice, invoice.currency_id, self.journal_id, self.payment_date)
+ amount = self.env['account.payment']._compute_payment_amount(invoices, invoices[0].currency_id, self.journal_id, self.payment_date)
values = {
'journal_id': self.journal_id.id,
'payment_method_id': self.payment_method_id.id,
'payment_date': self.payment_date,
- 'communication': invoice.ref or invoice.name,
- 'invoice_ids': [(6, 0, invoice.ids)],
+ 'communication': " ".join(i.ref or i.name for i in invoices),
+ 'invoice_ids': [(6, 0, invoices.ids)],
'payment_type': ('inbound' if amount > 0 else 'outbound'),
'amount': abs(amount),
- 'currency_id': invoice.currency_id.id,
- 'partner_id': invoice.commercial_partner_id.id,
- 'partner_type': MAP_INVOICE_TYPE_PARTNER_TYPE[invoice.type],
- 'partner_bank_account_id': invoice.invoice_partner_bank_id.id,
+ 'currency_id': invoices[0].currency_id.id,
+ 'partner_id': invoices[0].commercial_partner_id.id,
+ 'partner_type': MAP_INVOICE_TYPE_PARTNER_TYPE[invoices[0].type],
+ 'partner_bank_account_id': invoices[0].invoice_partner_bank_id.id,
}
return values
@@ -800,7 +806,13 @@ class payment_register(models.TransientModel):
:return: a list of payment values (dictionary).
'''
- return [self._prepare_payment_vals(invoice) for invoice in self.invoice_ids]
+ grouped = defaultdict(lambda: self.env["account.move"])
+ for inv in self.invoice_ids:
+ if self.group_payment:
+ grouped[(inv.commercial_partner_id, inv.currency_id, inv.partner_bank_id, MAP_INVOICE_TYPE_PARTNER_TYPE[inv.type])] += inv
+ else:
+ grouped[inv.id] += inv
+ return [self._prepare_payment_vals(invoices) for invoices in grouped.values()]
@api.multi
def create_payments(self):
diff --git a/addons/account/views/account_payment_view.xml b/addons/account/views/account_payment_view.xml
index 06da229a048..8b86ab80721 100644
--- a/addons/account/views/account_payment_view.xml
+++ b/addons/account/views/account_payment_view.xml
@@ -202,6 +202,7 @@
<group>
<group>
<field name="payment_method_id" widget="radio"/>
+ <field name="group_payment"/>
</group>
<group>
<field name="journal_id" widget="selection"/>
diff --git a/addons/hr_recruitment/models/hr_job.py b/addons/hr_recruitment/models/hr_job.py
index 280a096b5df..72af7f2774c 100644
--- a/addons/hr_recruitment/models/hr_job.py
+++ b/addons/hr_recruitment/models/hr_job.py
@@ -86,13 +86,10 @@ class Job(models.Model):
('job_id', '=', self.id)], order='sequence asc', limit=1)
def _compute_new_application_count(self):
- first_stages = {job.id: job._get_first_stage().id for job in self}
- mapped_data = dict.fromkeys(self.ids, 0)
- for applicant in self.mapped('application_ids'):
- if applicant.stage_id.id == first_stages.get(applicant.job_id.id):
- mapped_data[applicant.job_id.id] += 1
for job in self:
- job.new_application_count = mapped_data.get(job.id)
+ job.new_application_count = self.env["hr.applicant"].search_count(
+ [("job_id", "=", job.id), ("stage_id", "=", job._get_first_stage().id)]
+ )
def get_alias_model_name(self, vals):
return 'hr.applicant'
diff --git a/addons/l10n_ae/data/account_tax_template_data.xml b/addons/l10n_ae/data/account_tax_template_data.xml
index f95b45efa0e..a9ffc836bdb 100644
--- a/addons/l10n_ae/data/account_tax_template_data.xml
+++ b/addons/l10n_ae/data/account_tax_template_data.xml
@@ -826,7 +826,6 @@
<field name="account_id" ref="uae_account_3726"/>
<field name="refund_account_id" ref="uae_account_3726"/>
<field name="tax_group_id" ref="ae_tax_group_5"/>
- <field name="tag_ids" eval="[(6,0,[ref('tag_ae_02')])]"/>
<field name="chart_template_id" ref="uae_chart_template_standard"/>
</record>
<record id="uae_purchase_tax_reverse_charge" model="account.tax.template">
diff --git a/addons/mail/static/src/js/services/mail_window_manager.js b/addons/mail/static/src/js/services/mail_window_manager.js
index 81f0ef7e97c..5cc965b5c7a 100644
--- a/addons/mail/static/src/js/services/mail_window_manager.js
+++ b/addons/mail/static/src/js/services/mail_window_manager.js
@@ -114,9 +114,9 @@ MailManager.include({
thread.isCreatingWindow = true;
prom = thread.fetchMessages().then(function () {
threadWindow = self._makeNewThreadWindow(thread, options);
- self._placeNewThreadWindow(threadWindow, options.passively);
return threadWindow.appendTo($(self.THREAD_WINDOW_APPENDTO));
}).then(function () {
+ self._placeNewThreadWindow(threadWindow, options.passively);
self._repositionThreadWindows();
threadWindow.render();
threadWindow.scrollToBottom();
diff --git a/addons/mail/static/tests/thread_window/basic_thread_window_tests.js b/addons/mail/static/tests/thread_window/basic_thread_window_tests.js
index 7c3cfbdc793..3200ae38d85 100644
--- a/addons/mail/static/tests/thread_window/basic_thread_window_tests.js
+++ b/addons/mail/static/tests/thread_window/basic_thread_window_tests.js
@@ -16,6 +16,29 @@ QUnit.module('Basic', {
// define channel to link to chat window
this.data = {
+ 'mail.channel': {
+ fields: {
+ name: {
+ string: "Name",
+ type: "char",
+ required: true,
+ },
+ channel_type: {
+ string: "Channel Type",
+ type: "selection",
+ },
+ channel_message_ids: {
+ string: "Messages",
+ type: "many2many",
+ relation: 'mail.message'
+ },
+ message_unread_counter: {
+ string: "Amount of Unread Messages",
+ type: "integer"
+ },
+ },
+ records: [],
+ },
'mail.message': {
fields: {},
records: [],
@@ -292,34 +315,12 @@ QUnit.test('do not increment unread counter with focus on thread window', async
QUnit.test('do not mark as read the newly open thread window from received message', async function (assert) {
assert.expect(5);
- this.data['mail.channel'] = {
- fields: {
- name: {
- string: "Name",
- type: "char",
- required: true,
- },
- channel_type: {
- string: "Channel Type",
- type: "selection",
- },
- channel_message_ids: {
- string: "Messages",
- type: "many2many",
- relation: 'mail.message'
- },
- message_unread_counter: {
- string: "Amount of Unread Messages",
- type: "integer"
- },
- },
- records: [{
- id: 2,
- name: "DM",
- channel_type: "chat",
- message_unread_counter: 0,
- }],
- };
+ this.data['mail.channel'].records = [{
+ id: 2,
+ name: "DM",
+ channel_type: "chat",
+ message_unread_counter: 0,
+ }];
var parent = this.createParent({
data: this.data,
@@ -371,33 +372,11 @@ QUnit.test('do not mark as read the newly open thread window from received messa
QUnit.test('show document link of message linked to a document', async function (assert) {
assert.expect(6);
- this.data['mail.channel'] = {
- fields: {
- name: {
- string: "Name",
- type: "char",
- required: true,
- },
- channel_type: {
- string: "Channel Type",
- type: "selection",
- },
- channel_message_ids: {
- string: "Messages",
- type: "many2many",
- relation: 'mail.message'
- },
- message_unread_counter: {
- string: "Amount of Unread Messages",
- type: "integer"
- },
- },
- records: [{
- id: 2,
- name: "R&D Tasks",
- channel_type: "channel",
- }],
- };
+ this.data['mail.channel'].records = [{
+ id: 2,
+ name: "R&D Tasks",
+ channel_type: "channel",
+ }];
this.data['mail.message'].records.push({
author_id: [5, "Someone else"],
body: "<p>Test message</p>",
@@ -460,35 +439,13 @@ QUnit.test('do not autofocus chat window on receiving new direct message', async
}],
};
- this.data['mail.channel'] = {
- fields: {
- name: {
- string: "Name",
- type: "char",
- required: true,
- },
- channel_type: {
- string: "Channel Type",
- type: "selection",
- },
- channel_message_ids: {
- string: "Messages",
- type: "many2many",
- relation: 'mail.message'
- },
- message_unread_counter: {
- string: "Amount of Unread Messages",
- type: "integer"
- },
- },
- records: [{
- id: 2,
- name: "DM",
- channel_type: "chat",
- message_unread_counter: 0,
- direct_partner: [{ id: 666, name: 'DemoUser1', im_status: '' }],
- }],
- };
+ this.data['mail.channel'].records = [{
+ id: 2,
+ name: "DM",
+ channel_type: "chat",
+ message_unread_counter: 0,
+ direct_partner: [{ id: 666, name: 'DemoUser1', im_status: '' }],
+ }];
var form = await testUtils.createView({
View: FormView,
@@ -543,37 +500,15 @@ QUnit.test('do not auto-focus chat window on receiving new message from new DM',
assert.expect(10);
var self = this;
- this.data['mail.channel'] = {
- fields: {
- name: {
- string: "Name",
- type: "char",
- required: true,
- },
- channel_type: {
- string: "Channel Type",
- type: "selection",
- },
- channel_message_ids: {
- string: "Messages",
- type: "many2many",
- relation: 'mail.message'
- },
- message_unread_counter: {
- string: "Amount of Unread Messages",
- type: "integer"
- },
- },
- records: [{
- id: 2,
- name: "DM",
- channel_type: "chat",
- message_unread_counter: 1,
- direct_partner: [{ id: 666, name: 'DemoUser1', im_status: '' }],
- is_minimized: false,
- state: 'open',
- }],
- };
+ this.data['mail.channel'].records = [{
+ id: 2,
+ name: "DM",
+ channel_type: "chat",
+ message_unread_counter: 1,
+ direct_partner: [{ id: 666, name: 'DemoUser1', im_status: '' }],
+ is_minimized: false,
+ state: 'open',
+ }];
var parent = this.createParent({
data: this.data,
@@ -654,21 +589,15 @@ QUnit.test('do not auto-focus chat window on receiving new message from new DM',
QUnit.test('out-of-office status in thread window', async function (assert) {
assert.expect(1);
- this.data = {
- 'mail.message': {
- fields: {},
- records: [],
- },
- initMessaging: {
- channel_slots: {
- channel_channel: [{
- id: 1,
- name: "DM",
- channel_type: "chat",
- message_unread_counter: 0,
- direct_partner: [{ id: 666, name: 'DemoUser1', im_status: 'online', out_of_office_message: 'Please don\'t disturb'}],
- }],
- },
+ this.data.initMessaging = {
+ channel_slots: {
+ channel_channel: [{
+ id: 1,
+ name: "DM",
+ channel_type: "chat",
+ message_unread_counter: 0,
+ direct_partner: [{ id: 666, name: 'DemoUser1', im_status: 'online', out_of_office_message: 'Please don\'t disturb'}],
+ }],
},
};
var parent = this.createParent({
@@ -691,21 +620,15 @@ QUnit.test('out-of-office status in thread window', async function (assert) {
QUnit.test('no out-of-office status in thread window', async function (assert) {
assert.expect(1);
- this.data = {
- 'mail.message': {
- fields: {},
- records: [],
- },
- initMessaging: {
- channel_slots: {
- channel_channel: [{
- id: 1,
- name: "DM",
- channel_type: "chat",
- message_unread_counter: 0,
- direct_partner: [{ id: 666, name: 'DemoUser1', im_status: 'online'}],
- }],
- },
+ this.data.initMessaging = {
+ channel_slots: {
+ channel_channel: [{
+ id: 1,
+ name: "DM",
+ channel_type: "chat",
+ message_unread_counter: 0,
+ direct_partner: [{ id: 666, name: 'DemoUser1', im_status: 'online'}],
+ }],
},
};
var parent = this.createParent({
@@ -734,25 +657,19 @@ QUnit.test('auto-update out-of-office info on im_status change', async function
var timeoutMock = mailTestUtils.patchMailTimeouts();
var step = 0;
- this.data = {
- 'mail.message': {
- fields: {},
- records: [],
- },
- initMessaging: {
- channel_slots: {
- channel_channel: [{
- id: 1,
- name: "DM",
- channel_type: "chat",
- message_unread_counter: 0,
- direct_partner: [{
- id: 666,
- name: 'DemoUser1',
- im_status: 'online',
- }],
+ this.data.initMessaging = {
+ channel_slots: {
+ channel_channel: [{
+ id: 1,
+ name: "DM",
+ channel_type: "chat",
+ message_unread_counter: 0,
+ direct_partner: [{
+ id: 666,
+ name: 'DemoUser1',
+ im_status: 'online',
}],
- },
+ }],
},
};
@@ -839,6 +756,100 @@ QUnit.test('auto-update out-of-office info on im_status change', async function
parent.destroy();
});
+QUnit.test('receive 2 new DM messages in quick succession (no chat window initially)', async function (assert) {
+ assert.expect(3);
+
+ const self = this;
+ this.data['mail.channel'].records = [{
+ channel_type: "chat",
+ direct_partner: [{
+ id: 5,
+ name: 'Someone else',
+ im_status: 'online',
+ }],
+ id: 10,
+ is_minimized: false,
+ message_unread_counter: 1,
+ name: "DM",
+ state: 'open',
+ }];
+
+ this.data.initMessaging.channel_slots = {
+ channel_direct_message: [{
+ channel_type: 'chat',
+ direct_partner: [{
+ id: 5,
+ name: 'Someone else',
+ im_status: 'online',
+ }],
+ id: 10,
+ message_unread_counter: 0,
+ name: "DM",
+ }],
+ };
+
+ const parent = this.createParent({
+ data: this.data,
+ mockRPC(route, args) {
+ if (args.method === 'channel_minimize') {
+ Object.assign(self.data['mail.channel'].records[0], {
+ is_minimized: true,
+ state: 'open',
+ });
+ }
+ return this._super(...arguments);
+ },
+ services: this.services,
+ session: {
+ partner_id: 3,
+ },
+ });
+
+ await testUtils.nextTick();
+ assert.containsNone(
+ $,
+ '.o_thread_window',
+ "should not have any DM window open");
+
+ // simulate receiving 2 new messages from someone else in quick succession
+ const messageData1 = {
+ author_id: [5, "Someone else"],
+ body: "<p>Test message1</p>",
+ channel_ids: [10],
+ id: 2,
+ model: 'mail.channel',
+ res_id: 10,
+ };
+ this.data['mail.message'].records.push(messageData1);
+ const notification1 = [[false, 'mail.channel', 2], messageData1];
+ parent.call('bus_service', 'trigger', 'notification', [notification1]);
+ // simulate short delay for receiving new message
+ await testUtils.nextMicrotaskTick();
+ const messageData2 = {
+ author_id: [5, "Someone else"],
+ body: "<p>Test message2</p>",
+ channel_ids: [10],
+ id: 3,
+ model: 'mail.channel',
+ res_id: 10,
+ };
+ this.data['mail.message'].records.push(messageData2);
+ const notification2 = [[false, 'mail.channel', 2], messageData2];
+ parent.call('bus_service', 'trigger', 'notification', [notification2]);
+ await testUtils.nextTick();
+ assert.containsOnce(
+ $,
+ '.o_thread_window',
+ "should have DM window open");
+ assert.containsN(
+ $('.o_thread_window'),
+ '.o_thread_message',
+ 2,
+ "should have 2 messages in chat window");
+
+ parent.destroy();
+});
+
});
});
});
diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml
index 63efb729576..36ed2b159d7 100644
--- a/addons/point_of_sale/static/src/xml/pos.xml
+++ b/addons/point_of_sale/static/src/xml/pos.xml
@@ -828,7 +828,7 @@
<t t-esc="line.price_with_tax_before_discount" />
</t>
<t t-else="">
- <t t-esc="line.price_lst" />
+ <t t-esc="line.price" />
</t>
</value>
</left>
diff --git a/addons/point_of_sale/views/pos_session_view.xml b/addons/point_of_sale/views/pos_session_view.xml
index d1c3ada8e97..c2a047cddb5 100644
--- a/addons/point_of_sale/views/pos_session_view.xml
+++ b/addons/point_of_sale/views/pos_session_view.xml
@@ -135,6 +135,8 @@
</sheet>
<div class="oe_chatter">
<field name="activity_ids" widget="mail_activity"/>
+ <field name="message_follower_ids" widget="mail_followers"/>
+ <field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>
diff --git a/addons/project_timesheet_holidays/models/hr_holidays.py b/addons/project_timesheet_holidays/models/hr_holidays.py
index ada3b56acc1..e78420a0176 100644
--- a/addons/project_timesheet_holidays/models/hr_holidays.py
+++ b/addons/project_timesheet_holidays/models/hr_holidays.py
@@ -83,6 +83,7 @@ class Holidays(models.Model):
'date': fields.Date.to_string(day_date),
'holiday_id': holiday.id,
'employee_id': holiday.employee_id.id,
+ 'company_id': holiday_task.company_id.id or holiday_project.company_id.id,
})
return super(Holidays, self)._validate_leave_request()
diff --git a/addons/project_timesheet_holidays/models/res_company.py b/addons/project_timesheet_holidays/models/res_company.py
index 948b4f3bffa..4ec9bcf4e19 100644
--- a/addons/project_timesheet_holidays/models/res_company.py
+++ b/addons/project_timesheet_holidays/models/res_company.py
@@ -40,7 +40,7 @@ class Company(models.Model):
'name': _('Time Off'),
'project_id': company.leave_timesheet_project_id.id,
'active': False,
- 'company_id': False,
+ 'company_id': company.id,
})
company.write({
'leave_timesheet_task_id': task.id,
diff --git a/addons/web/static/src/js/views/control_panel/control_panel_model.js b/addons/web/static/src/js/views/control_panel/control_panel_model.js
index 0229daa41ee..02e1c49508b 100644
--- a/addons/web/static/src/js/views/control_panel/control_panel_model.js
+++ b/addons/web/static/src/js/views/control_panel/control_panel_model.js
@@ -407,7 +407,17 @@ var ControlPanelModel = mvc.Model.extend({
// we need to deactivate all groups when activating a favorite
if (filter.type === 'favorite') {
this.query.forEach(function (groupId) {
- self.groups[groupId].activeFilterIds = [];
+ var group = self.groups[groupId];
+ group.activeFilterIds.forEach(function (id) {
+ var filter = self.filters[id];
+ if (filter.autoCompleteValues) {
+ filter.autoCompleteValues = [];
+ }
+ if (filter.currentOptionId) {
+ filter.currentOptionId = false;
+ }
+ })
+ group.activeFilterIds = [];
});
this.query = [];
}
diff --git a/addons/web/static/src/js/views/control_panel/search/add_new_favorite_menu.js b/addons/web/static/src/js/views/control_panel/search/add_new_favorite_menu.js
index 421fec3e04b..07c80d38c29 100644
--- a/addons/web/static/src/js/views/control_panel/search/add_new_favorite_menu.js
+++ b/addons/web/static/src/js/views/control_panel/search/add_new_favorite_menu.js
@@ -40,6 +40,12 @@ var AddNewFavoriteMenu = Widget.extend({
//--------------------------------------------------------------------------
/**
+ * @param {Object} params
+ */
+ update: function (params) {
+ this.favorites = params.favorites;
+ },
+ /**
* Close the menu and re-render the widget.
*/
closeMenu: function () {
diff --git a/addons/web/static/src/js/views/control_panel/search/favorite_menu.js b/addons/web/static/src/js/views/control_panel/search/favorite_menu.js
index 8b9ae4d44d6..0a536820735 100644
--- a/addons/web/static/src/js/views/control_panel/search/favorite_menu.js
+++ b/addons/web/static/src/js/views/control_panel/search/favorite_menu.js
@@ -49,6 +49,18 @@ var FavoriteMenu = DropdownMenu.extend({
},
//--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ update: function (items) {
+ this._super.apply(this, arguments);
+ _.invoke(this.subMenus, 'update', { favorites: this.items });
+ },
+
+ //--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
diff --git a/addons/web/static/src/js/widgets/domain_selector.js b/addons/web/static/src/js/widgets/domain_selector.js
index ed71679699c..755ee32979e 100644
--- a/addons/web/static/src/js/widgets/domain_selector.js
+++ b/addons/web/static/src/js/widgets/domain_selector.js
@@ -652,7 +652,7 @@ var DomainLeaf = DomainNode.extend({
this.fieldSelector = new ModelFieldSelector(
this,
this.model,
- this.chain ? this.chain.split(".") : [],
+ this.chain !== undefined ? this.chain.toString().split(".") : [],
this.options
);
defs.push(this.fieldSelector.appendTo($("<div/>")).then((function () {
diff --git a/addons/web/static/src/js/widgets/model_field_selector.js b/addons/web/static/src/js/widgets/model_field_selector.js
index 08a7ae47c81..ed74b2da44a 100644
--- a/addons/web/static/src/js/widgets/model_field_selector.js
+++ b/addons/web/static/src/js/widgets/model_field_selector.js
@@ -301,11 +301,16 @@ var ModelFieldSelector = Widget.extend({
}).bind(this));
function processChain(chain) {
- var field = this._getLastPageField(chain.pop());
+ var fieldName = chain.pop();
+ var field = this._getLastPageField(fieldName);
if (field && field.relation && chain.length > 0) { // Fetch next chain node if any and possible
return this._pushPageData(field.relation).then(processChain.bind(this, chain));
} else if (field && chain.length === 0) { // Last node fetched
return Promise.resolve();
+ } else if (!field && fieldName === "1") { // TRUE_LEAF
+ this._validate(true);
+ } else if (!field && fieldName === "0") { // FALSE_LEAF
+ this._validate(true);
} else { // Wrong node chain
this._validate(false);
}
diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml
index bb744cbdb03..5eff2fb4422 100644
--- a/addons/web/static/src/xml/base.xml
+++ b/addons/web/static/src/xml/base.xml
@@ -650,8 +650,16 @@
<i class="fa fa-chevron-right" role="img" aria-label="Followed by" title="Followed by"/>
</t>
<span class="o_field_selector_chain_part">
- <t t-set="fieldInfo" t-value="_.findWhere(pages[fieldName_index], {'name': fieldName})"/>
- <t t-esc="fieldInfo &amp;&amp; fieldInfo.string || '?'"/>
+ <t t-if="fieldName === '1'">
+ <t t-esc="fieldName"/>
+ </t>
+ <t t-elif="fieldName === '0'">
+ <t t-esc="fieldName"/>
+ </t>
+ <t t-else="">
+ <t t-set="fieldInfo" t-value="_.findWhere(pages[fieldName_index], {'name': fieldName})"/>
+ <t t-esc="fieldInfo &amp;&amp; fieldInfo.string || '?'"/>
+ </t>
</span>
</t>
</t>
diff --git a/addons/web/static/tests/views/search_view_tests.js b/addons/web/static/tests/views/search_view_tests.js
index ccf5bf5b50d..fc225818ad6 100644
--- a/addons/web/static/tests/views/search_view_tests.js
+++ b/addons/web/static/tests/views/search_view_tests.js
@@ -8,6 +8,7 @@ var createActionManager = testUtils.createActionManager;
var createControlPanel = testUtils.createControlPanel;
var createView = testUtils.createView;
var patchDate = testUtils.mock.patchDate;
+var session = require('web.session');
var controlPanelViewParameters = require('web.controlPanelViewParameters');
var PERIOD_OPTIONS_IDS = controlPanelViewParameters.PERIOD_OPTIONS.map(function (option) {return option.optionId;});
@@ -119,6 +120,14 @@ QUnit.module('Search View', {
type: 'ir.actions.act_window',
views: [[2, 'list']],
search_view_id: [8, 'search'],
+ },
+ {
+ id: 12,
+ name: 'Partners Action 12',
+ res_model: 'partner',
+ type: 'ir.actions.act_window',
+ views: [[2, 'list']],
+ search_view_id: [9, 'search'],
}
];
@@ -198,6 +207,11 @@ QUnit.module('Search View', {
'<filter string="Date Field Filter" name="positive" date="date_field"/>' +
'<filter string="Date Field Groupby" name="coolName" context="{\'group_by\': \'date_field:day\'}"/>' +
'</search>',
+ 'partner,9,search': '<search>'+
+ '<field string="Foo" name="foo"/>' +
+ '<filter string="Date Field Filter" name="positive" date="date_field"/>' +
+ '<filter string="Date Field Groupby" name="coolName" context="{\'group_by\': \'date_field:day\'}"/>' +
+ '</search>',
};
@@ -959,6 +973,159 @@ QUnit.module('Search View', {
controlPanel.destroy();
});
+ QUnit.test('toggle favorite correctly clears filter, groupbys and field "options"', async function (assert) {
+ assert.expect(8);
+
+ const actionManager = await createActionManager({
+ actions: this.actions,
+ archs: this.archs,
+ data: this.data,
+ intercepts: {
+ load_filters: function (event) {
+ return Promise.resolve([{
+ context: "{}",
+ domain: "[]",
+ id: 7,
+ is_default: false,
+ name: "My favorite",
+ sort: "[]",
+ user_id: [2, "Mitchell Admin"],
+ }]).then(event.data.on_success);
+ },
+ },
+ });
+
+ await actionManager.doAction(12);
+
+ // activate Foo a
+ await testUtils.fields.triggerKey('press', $('.o_searchview_input'), 97);
+ await testUtils.fields.triggerKey('up', $('.o_searchview_input'), 'enter');
+
+ // activate Date Filter with This Month
+ await testUtils.dom.click($('button .fa-filter'));
+ await testUtils.dom.click($('.o_filters_menu .o_menu_item a'));
+ await testUtils.dom.click($('.o_item_option[data-option_id="this_month"]'));
+
+ // activate Date Groupby with Year
+ await testUtils.dom.click($('button .fa-bars'));
+ await testUtils.dom.click($('.o_group_by_menu .o_menu_item a'));
+ await testUtils.dom.click($('.o_item_option[data-option_id="year"]'));
+
+ assert.strictEqual($('.o_searchview_input_container .o_facet_values').eq(0).text().trim(),
+ "a",
+ "There should be a filter Foo a");
+
+ assert.strictEqual($('.o_searchview_input_container .o_facet_values').eq(1).text().trim(),
+ "Date Field Filter: This Month",
+ "There should be a filter facet with label 'Date Field Filter: This Month'");
+
+ assert.strictEqual($('.o_searchview_input_container .o_facet_values').eq(2).text().trim(),
+ "Date Field Groupby: Year",
+ "There should be a groupby facet with label 'Date Field Groupby: Year'");
+
+ // activate the favorite
+ await testUtils.dom.click($('button .fa-star'));
+ await testUtils.dom.click($('.o_favorites_menu .o_menu_item a'));
+
+ assert.containsOnce(actionManager, '.o_searchview_input_container .o_facet_values',
+ "There should be a unique facet in the search bar");
+ assert.strictEqual($('.o_searchview_input_container .o_facet_values').eq(0).text().trim(),
+ "My favorite",
+ "There should be a facet with label 'My favorite''");
+
+ await testUtils.dom.click($('button .fa-filter'));
+ await testUtils.dom.click($('.o_filters_menu .o_menu_item a'));
+ assert.doesNotHaveClass($('.o_item_option[data-option_id="this_month"]'), 'selected',
+ "The Date Filter option 'This Month' should be deactivated");
+
+ await testUtils.dom.click($('button .fa-bars'));
+ await testUtils.dom.click($('.o_group_by_menu .o_menu_item a'));
+ assert.doesNotHaveClass($('.o_item_option[data-option_id="year"]'), 'selected',
+ "The Date Groupby option 'Year' should be deactivated");
+
+ await testUtils.fields.triggerKey('press', $('.o_searchview_input'), 98);
+ await testUtils.fields.triggerKey('up', $('.o_searchview_input'), 'enter');
+ assert.strictEqual($('.o_searchview_input_container .o_facet_values').eq(1).text().trim(),
+ "b",
+ "There should be a filter Foo b");
+
+ actionManager.destroy();
+ });
+
+ QUnit.test('favorites have unique descriptions (the submenus of the favorite menu are correctly updated)', async function (assert) {
+ assert.expect(7);
+
+ const UID = session.uid;
+
+ const actionManager = await createActionManager({
+ actions: this.actions,
+ archs: this.archs,
+ data: this.data,
+ intercepts: {
+ load_filters: function (event) {
+ return Promise.resolve([{
+ context: "{}",
+ domain: "[]",
+ id: 7,
+ is_default: false,
+ name: "My favorite",
+ sort: "[]",
+ user_id: [2, "Mitchell Admin"],
+ }]).then(event.data.on_success);
+ },
+ create_filter: function (event) {
+ assert.step('create_filter');
+ assert.deepEqual(event.data.filter, {
+ "action_id": 1,
+ "context": {},
+ "domain": "[]",
+ "is_default": false,
+ "model_id": "partner",
+ "name": "My favorite 2",
+ "sort": "[]",
+ "user_id": UID,
+ });
+ return Promise.resolve(1).then(event.data.on_success);
+ }
+ },
+ });
+ testUtils.mock.intercept(actionManager, 'call_service', function (ev) {
+ if (ev.data.service === 'notification') {
+ // A notification alerting that another favorite with same name exists
+ // should be triggered
+ assert.step('notification');
+ assert.deepEqual(ev.data.args[0], {
+ "className": undefined,
+ "message": "Filter with same name already exists.",
+ "sticky": undefined,
+ "title": "Error",
+ "type": "warning"
+ });
+ }
+ }, true);
+
+ await actionManager.doAction(1);
+ await testUtils.dom.click($('button .fa-star'));
+ await testUtils.dom.click($('.o_favorites_menu .o_add_favorite'));
+ await testUtils.fields.editInput($('div.o_favorite_name input'), 'My favorite');
+ await testUtils.dom.click($('.o_favorites_menu div.o_save_favorite button'));
+
+ await testUtils.fields.editInput($('div.o_favorite_name input'), 'My favorite 2');
+ await testUtils.dom.click($('.o_favorites_menu div.o_save_favorite button'));
+
+ await testUtils.dom.click($('.o_favorites_menu .o_add_favorite'));
+ await testUtils.fields.editInput($('div.o_favorite_name input'), 'My favorite 2');
+ await testUtils.dom.click($('.o_favorites_menu div.o_save_favorite button'));
+
+ assert.verifySteps([
+ 'notification',
+ 'create_filter',
+ 'notification'
+ ])
+
+ actionManager.destroy();
+ });
+
QUnit.module('Search Arch');
QUnit.test('arch order of groups of filters preserved', async function (assert) {
diff --git a/addons/web/static/tests/widgets/model_field_selector_tests.js b/addons/web/static/tests/widgets/model_field_selector_tests.js
index 76c18b91f5b..c45bcb9dabe 100644
--- a/addons/web/static/tests/widgets/model_field_selector_tests.js
+++ b/addons/web/static/tests/widgets/model_field_selector_tests.js
@@ -248,6 +248,46 @@ QUnit.module('ModelFieldSelector', {
fieldSelector.destroy();
});
+
+ QUnit.test("create a field chain with value 1 i.e. TRUE_LEAF", async function (assert) {
+ assert.expect(1);
+
+ var $target = $("#qunit-fixture");
+
+ //create the field selector with domain value ["1"]
+ var fieldSelector = new ModelFieldSelector(null, "partner", ["1"], {
+ readonly: false,
+ showSearchInput: false,
+ });
+ testUtils.mock.addMockEnvironment(fieldSelector, {data: this.data});
+ await fieldSelector.appendTo($target);
+
+ var $fieldName = fieldSelector.$('.o_field_selector_chain_part');
+ assert.strictEqual($fieldName.text().trim(), "1",
+ "field name value should be 1.");
+
+ fieldSelector.destroy();
+ });
+
+ QUnit.test("create a field chain with value 0 i.e. FALSE_LEAF", async function (assert) {
+ assert.expect(1);
+
+ var $target = $("#qunit-fixture");
+
+ //create the field selector with domain value ["0"]
+ var fieldSelector = new ModelFieldSelector(null, "partner", ["0"], {
+ readonly: false,
+ showSearchInput: false,
+ });
+ testUtils.mock.addMockEnvironment(fieldSelector, {data: this.data});
+ await fieldSelector.appendTo($target);
+
+ var $fieldName = fieldSelector.$('.o_field_selector_chain_part');
+ assert.strictEqual($fieldName.text().trim(), "0",
+ "field name value should be 0.");
+
+ fieldSelector.destroy();
+ });
});
});
});
diff --git a/addons/website_profile/static/src/scss/website_profile.scss b/addons/website_profile/static/src/scss/website_profile.scss
index 8c3b572955f..ed7d2e2f330 100644
--- a/addons/website_profile/static/src/scss/website_profile.scss
+++ b/addons/website_profile/static/src/scss/website_profile.scss
@@ -62,7 +62,7 @@ $owprofile-color-bg: mix($body-bg, #efeff4);
}
&.active {
- color: $headings-color;
+ color: color-yiq($owprofile-color-bg);
background: $owprofile-color-bg;
border-color: $owprofile-color-bg;
}
@@ -213,4 +213,4 @@ $owprofile-color-bg: mix($body-bg, #efeff4);
// Tools
.o_wprofile_pointer {
cursor: pointer;
-} \ No newline at end of file
+}
diff --git a/addons/website_rating/static/src/scss/website_rating.scss b/addons/website_rating/static/src/scss/website_rating.scss
index 10d9d681a7d..4c2a70495fe 100644
--- a/addons/website_rating/static/src/scss/website_rating.scss
+++ b/addons/website_rating/static/src/scss/website_rating.scss
@@ -80,3 +80,7 @@ $o-w-rating-star-color: #FACC2E;
margin-right: 10px;
}
}
+
+.o_rating_popup_composer_label {
+ color: color-yiq(white);
+}
diff --git a/addons/website_rating/static/src/xml/portal_chatter.xml b/addons/website_rating/static/src/xml/portal_chatter.xml
index 5d0395c099b..cebaea338d5 100644
--- a/addons/website_rating/static/src/xml/portal_chatter.xml
+++ b/addons/website_rating/static/src/xml/portal_chatter.xml
@@ -70,7 +70,7 @@
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
- <h5 class="modal-title" id="ratingpopupcomposerlabel">
+ <h5 class="modal-title o_rating_popup_composer_label" id="ratingpopupcomposerlabel">
<t t-if="widget.options['default_message_id']">
Modify your review
</t>
diff --git a/addons/website_slides/static/src/scss/slides_slide_fullscreen.scss b/addons/website_slides/static/src/scss/slides_slide_fullscreen.scss
index ce306a53b21..1b7ff14ae61 100644
--- a/addons/website_slides/static/src/scss/slides_slide_fullscreen.scss
+++ b/addons/website_slides/static/src/scss/slides_slide_fullscreen.scss
@@ -1,7 +1,7 @@
.o_wslides_fs_main {
@include o-position-absolute(0,0,0,0);
- z-index: $zindex-dropdown - 1;
+ z-index: $zindex-dropdown;
background-image: linear-gradient(120deg, $o-wslides-color-dark2, $o-wslides-color-dark3);
.o_wslides_slide_fs_header {
@@ -114,4 +114,10 @@
pointer-events: none;
}
}
-} \ No newline at end of file
+}
+
+.modal-open {
+ > .modal-backdrop {
+ z-index: $zindex-dropdown - 1;
+ }
+}
diff --git a/addons/website_slides/static/src/scss/website_slides.scss b/addons/website_slides/static/src/scss/website_slides.scss
index 0f64a46ea8e..b661fcdab1f 100644
--- a/addons/website_slides/static/src/scss/website_slides.scss
+++ b/addons/website_slides/static/src/scss/website_slides.scss
@@ -156,7 +156,7 @@ $o-wslides-fs-side-width: 300px;
}
&.active {
- color: $headings-color;
+ color: color-yiq($tab-active-color);
background: $tab-active-color;
border-color: $tab-active-color;
}
diff --git a/addons/website_slides_forum/views/website_slides_forum_templates.xml b/addons/website_slides_forum/views/website_slides_forum_templates.xml
index ed0e278479a..3b622ba42cd 100644
--- a/addons/website_slides_forum/views/website_slides_forum_templates.xml
+++ b/addons/website_slides_forum/views/website_slides_forum_templates.xml
@@ -12,7 +12,7 @@
<i class="fa fa-home"/> Course</a>
</li>
<li class="nav-item">
- <a href="#" t-att-class="'nav-link active o_wprofile_navlink'" style="border-left: 0px">Forum</a>
+ <a t-att-href="'/forum/%s' % (slug(forum))" t-att-class="'nav-link active o_wprofile_navlink'" style="border-left: 0px">Forum</a>
</li>
<li class="nav-item">
<a t-att-href="'/slides/%s?active_tab=review' % (slug(forum.slide_channel_id))"