diff options
Diffstat (limited to 'addons')
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 && 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 && 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))" |
