[MERGE] forward port branch saas-12.3 up to 499d3efa7c
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 = [];
|
||||
}
|
||||
|
||||
@@ -39,6 +39,12 @@ var AddNewFavoriteMenu = Widget.extend({
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @param {Object} params
|
||||
*/
|
||||
update: function (params) {
|
||||
this.favorites = params.favorites;
|
||||
},
|
||||
/**
|
||||
* Close the menu and re-render the widget.
|
||||
*/
|
||||
|
||||
@@ -48,6 +48,18 @@ var FavoriteMenu = DropdownMenu.extend({
|
||||
});
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
update: function (items) {
|
||||
this._super.apply(this, arguments);
|
||||
_.invoke(this.subMenus, 'update', { favorites: this.items });
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,3 +80,7 @@ $o-w-rating-star-color: #FACC2E;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.o_rating_popup_composer_label {
|
||||
color: color-yiq(white);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-open {
|
||||
> .modal-backdrop {
|
||||
z-index: $zindex-dropdown - 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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))"
|
||||
|
||||
Reference in New Issue
Block a user