[MERGE] forward port branch 12.0 up to c1c322dd40
This commit is contained in:
@@ -20,18 +20,6 @@
|
||||
<field name="category_id" ref="base.module_category_hidden"/>
|
||||
</record>
|
||||
|
||||
<record id="base.group_public" model="res.groups">
|
||||
<field name="implied_ids" eval="[(4, ref('account.group_show_line_subtotals_tax_excluded'))]"/>
|
||||
</record>
|
||||
|
||||
<record id="base.group_portal" model="res.groups">
|
||||
<field name="implied_ids" eval="[(4, ref('account.group_show_line_subtotals_tax_excluded'))]"/>
|
||||
</record>
|
||||
|
||||
<record id="base.group_user" model="res.groups">
|
||||
<field name="implied_ids" eval="[(4, ref('account.group_show_line_subtotals_tax_excluded'))]"/>
|
||||
</record>
|
||||
|
||||
<record id="group_account_user" model="res.groups">
|
||||
<field name="name">Show Full Accounting Features</field>
|
||||
<field name="category_id" ref="base.module_category_hidden"/>
|
||||
@@ -75,6 +63,16 @@
|
||||
|
||||
|
||||
<data noupdate="1">
|
||||
<record id="base.group_public" model="res.groups">
|
||||
<field name="implied_ids" eval="[(4, ref('account.group_show_line_subtotals_tax_excluded'))]"/>
|
||||
</record>
|
||||
<record id="base.group_portal" model="res.groups">
|
||||
<field name="implied_ids" eval="[(4, ref('account.group_show_line_subtotals_tax_excluded'))]"/>
|
||||
</record>
|
||||
<record id="base.group_user" model="res.groups">
|
||||
<field name="implied_ids" eval="[(4, ref('account.group_show_line_subtotals_tax_excluded'))]"/>
|
||||
</record>
|
||||
|
||||
<record id="base.default_user" model="res.users">
|
||||
<field name="groups_id" eval="[(4,ref('account.group_account_manager'))]"/>
|
||||
</record>
|
||||
|
||||
@@ -51,12 +51,16 @@
|
||||
<table class="table table-sm o_main_table" name="invoice_line_table">
|
||||
<thead>
|
||||
<tr>
|
||||
<!-- TODO: remove in master -->
|
||||
<t t-set="colspan" t-value="6"/>
|
||||
<th class="text-left"><span>Description</span></th>
|
||||
<th class="d-none text-left"><span>Source Document</span></th>
|
||||
<th class="text-right"><span>Quantity</span></th>
|
||||
<th t-attf-class="text-right {{ 'd-none d-md-table-cell' if report_type == 'html' else '' }}"><span>Unit Price</span></th>
|
||||
<th t-if="display_discount" t-attf-class="text-right {{ 'd-none d-md-table-cell' if report_type == 'html' else '' }}">
|
||||
<span>Disc.(%)</span>
|
||||
<!-- TODO: remove in master -->
|
||||
<t t-set="colspan" t-value="colspan+1"/>
|
||||
</th>
|
||||
<th t-attf-class="text-left {{ 'd-none d-md-table-cell' if report_type == 'html' else '' }}"><span>Taxes</span></th>
|
||||
<th class="text-right">
|
||||
|
||||
@@ -123,8 +123,8 @@
|
||||
}
|
||||
|
||||
// Override height of graph. min-height doesn't do the trick
|
||||
.o_graph_controller {
|
||||
height: 500px;
|
||||
.o_graph_svg_container {
|
||||
height: 250px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ from odoo.addons.calendar.models.calendar import get_real_ids
|
||||
class Partner(models.Model):
|
||||
_inherit = 'res.partner'
|
||||
|
||||
calendar_last_notif_ack = fields.Datetime('Last notification marked as read from base Calendar')
|
||||
calendar_last_notif_ack = fields.Datetime('Last notification marked as read from base Calendar', default=fields.Datetime.now)
|
||||
|
||||
@api.multi
|
||||
def get_attendee_detail(self, meeting_id):
|
||||
|
||||
@@ -13,9 +13,22 @@ class IrAttachment(models.Model):
|
||||
"""
|
||||
super(IrAttachment, self)._post_add_create()
|
||||
for record in self:
|
||||
record.register_as_main_attachment()
|
||||
record.register_as_main_attachment(force=False)
|
||||
|
||||
def register_as_main_attachment(self):
|
||||
@api.multi
|
||||
def unlink(self):
|
||||
self.remove_as_main_attachment()
|
||||
super(IrAttachment, self).unlink()
|
||||
|
||||
@api.multi
|
||||
def remove_as_main_attachment(self):
|
||||
for attachment in self:
|
||||
related_record = self.env[attachment.res_model].browse(attachment.res_id)
|
||||
if related_record and hasattr(related_record, 'message_main_attachment_id'):
|
||||
if related_record.message_main_attachment_id == attachment:
|
||||
related_record.message_main_attachment_id = False
|
||||
|
||||
def register_as_main_attachment(self, force=True):
|
||||
""" Registers this attachment as the main one of the model it is
|
||||
attached to.
|
||||
"""
|
||||
@@ -24,4 +37,5 @@ class IrAttachment(models.Model):
|
||||
# message_main_attachment_id field can be empty, that's why we compare to False;
|
||||
# we are just checking that it exists on the model before writing it
|
||||
if related_record and hasattr(related_record, 'message_main_attachment_id'):
|
||||
related_record.message_main_attachment_id = self
|
||||
if force or not related_record.message_main_attachment_id:
|
||||
related_record.message_main_attachment_id = self
|
||||
|
||||
@@ -81,6 +81,7 @@ var Chatter = Widget.extend({
|
||||
var nodeOptions = fieldsInfo[mailFields.mail_thread].options || {};
|
||||
this.hasLogButton = options.display_log_button || nodeOptions.display_log_button;
|
||||
this.postRefresh = nodeOptions.post_refresh || 'never';
|
||||
this.reloadOnUploadAttachment = this.postRefresh === 'always';
|
||||
}
|
||||
},
|
||||
/**
|
||||
@@ -572,6 +573,9 @@ var Chatter = Widget.extend({
|
||||
* @private
|
||||
*/
|
||||
_onReloadAttachmentBox: function () {
|
||||
if (this.reloadOnUploadAttachment) {
|
||||
this.trigger_up('reload');
|
||||
}
|
||||
this._reloadAttachmentBox();
|
||||
},
|
||||
/**
|
||||
|
||||
@@ -189,7 +189,7 @@ class MrpProductProduceLine(models.TransientModel):
|
||||
|
||||
product_produce_id = fields.Many2one('mrp.product.produce')
|
||||
product_id = fields.Many2one('product.product', 'Product')
|
||||
product_tracking = fields.Selection(related="product_id.tracking", readonly=False)
|
||||
product_tracking = fields.Selection(related="product_id.tracking")
|
||||
lot_id = fields.Many2one('stock.production.lot', 'Lot/Serial Number')
|
||||
qty_to_consume = fields.Float('To Consume', digits=dp.get_precision('Product Unit of Measure'))
|
||||
product_uom_id = fields.Many2one('uom.uom', 'Unit of Measure')
|
||||
|
||||
@@ -523,6 +523,7 @@ class ProductTemplate(models.Model):
|
||||
variants_to_create.append({
|
||||
'product_tmpl_id': tmpl_id.id,
|
||||
'attribute_value_ids': [(6, 0, list(value_ids))],
|
||||
'active': tmpl_id.active,
|
||||
})
|
||||
if len(variants_to_create) > 1000:
|
||||
raise UserError(_(
|
||||
@@ -962,8 +963,10 @@ class ProductTemplate(models.Model):
|
||||
if not self.active:
|
||||
return _("The product template is archived so no combination is possible.")
|
||||
|
||||
ptal_stack = [self._get_valid_product_template_attribute_lines()]
|
||||
combination_stack = [self.env['product.template.attribute.value']]
|
||||
necessary_values = necessary_values or self.env['product.template.attribute.value']
|
||||
necessary_attributes = necessary_values.mapped('attribute_id')
|
||||
ptal_stack = [self.valid_product_template_attribute_line_ids.filtered(lambda ptal: ptal.attribute_id not in necessary_attributes)]
|
||||
combination_stack = [necessary_values]
|
||||
|
||||
# keep going while we have attribute lines to test
|
||||
while len(ptal_stack):
|
||||
@@ -972,7 +975,7 @@ class ProductTemplate(models.Model):
|
||||
|
||||
if not attribute_lines:
|
||||
# full combination, if it's possible return it, otherwise skip it
|
||||
if self._is_combination_possible(combination, parent_combination) and all(v in combination for v in (necessary_values or [])):
|
||||
if self._is_combination_possible(combination, parent_combination):
|
||||
yield(combination)
|
||||
else:
|
||||
# we have remaining attribute lines to consider
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import time
|
||||
|
||||
from odoo.tests import tagged
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
@@ -423,6 +425,47 @@ class TestProductAttributeValueConfig(TestProductAttributeValueSetup):
|
||||
self.assertEqual(self.computer._get_closest_possible_combination(computer_ssd_256 + computer_ram_8 + computer_hdd_4 + computer_hdd_2),
|
||||
computer_ssd_256 + computer_ram_8 + computer_hdd_4)
|
||||
|
||||
# Make sure this is not extremely slow:
|
||||
product_template = self.env['product.template'].create({
|
||||
'name': 'many combinations',
|
||||
})
|
||||
|
||||
for i in range(10):
|
||||
# create the attributes
|
||||
product_attribute = self.env['product.attribute'].create({
|
||||
'name': "att %s" % i,
|
||||
'create_variant': 'dynamic',
|
||||
'sequence': i,
|
||||
})
|
||||
|
||||
for j in range(10):
|
||||
# create the attribute values
|
||||
self.env['product.attribute.value'].create([{
|
||||
'name': "val %s/%s" % (i, j),
|
||||
'attribute_id': product_attribute.id,
|
||||
'sequence': j,
|
||||
}])
|
||||
|
||||
# set attribute and attribute values on the template
|
||||
self.env['product.template.attribute.line'].create([{
|
||||
'attribute_id': product_attribute.id,
|
||||
'product_tmpl_id': product_template.id,
|
||||
'value_ids': [(6, 0, product_attribute.value_ids.ids)]
|
||||
}])
|
||||
|
||||
# Get a value in the middle for each attribute to make sure it would
|
||||
# take time to reach it (if looping one by one like before the fix).
|
||||
combination = self.env['product.template.attribute.value']
|
||||
for ptal in product_template.attribute_line_ids:
|
||||
combination += ptal.product_template_value_ids[5]
|
||||
|
||||
started_at = time.time()
|
||||
self.assertEqual(product_template._get_closest_possible_combination(combination), combination)
|
||||
elapsed = time.time() - started_at
|
||||
# It should take around 10ms, but to avoid false positives we check an
|
||||
# higher value. Before the fix it would take hours.
|
||||
self.assertLess(elapsed, 0.5)
|
||||
|
||||
def test_clear_caches(self):
|
||||
"""The goal of this test is to make sure the cache is invalidated when
|
||||
it should be."""
|
||||
|
||||
@@ -70,11 +70,15 @@
|
||||
<table class="table table-sm o_main_table">
|
||||
<thead>
|
||||
<tr>
|
||||
<!-- TODO: remove in master -->
|
||||
<t t-set="colspan" t-value="5"/>
|
||||
<th class="text-left">Description</th>
|
||||
<th class="text-right">Quantity</th>
|
||||
<th class="text-right">Unit Price</th>
|
||||
<th t-if="display_discount" class="text-right" groups="sale.group_discount_per_so_line">
|
||||
<span>Disc.(%)</span>
|
||||
<!-- TODO: remove in master -->
|
||||
<t t-set="colspan" t-value="colspan+1"/>
|
||||
</th>
|
||||
<th class="text-right">Taxes</th>
|
||||
<th class="text-right">
|
||||
|
||||
@@ -423,11 +423,15 @@
|
||||
<table class="table table-sm" id="sales_order_table">
|
||||
<thead class="bg-100">
|
||||
<tr>
|
||||
<!-- TODO: remove in master -->
|
||||
<t t-set="colspan" t-value="6"/>
|
||||
<th class="text-left">Products</th>
|
||||
<th class="text-right">Quantity</th>
|
||||
<th t-attf-class="text-right {{ 'd-none d-sm-table-cell' if report_type == 'html' else '' }}">Unit Price</th>
|
||||
<th t-if="display_discount" t-attf-class="text-right {{ 'd-none d-sm-table-cell' if report_type == 'html' else '' }}">
|
||||
<span>Discount</span>
|
||||
<!-- TODO: remove in master -->
|
||||
<t t-set="colspan" t-value="colspan+1"/>
|
||||
</th>
|
||||
<th t-attf-class="text-right {{ 'd-none d-md-table-cell' if report_type == 'html' else '' }}">Taxes</th>
|
||||
<th class="text-right" >
|
||||
|
||||
@@ -31,7 +31,8 @@ class SaleOrder(models.Model):
|
||||
@api.onchange('partner_id')
|
||||
def onchange_partner_id(self):
|
||||
super(SaleOrder, self).onchange_partner_id()
|
||||
self.note = self.sale_order_template_id.note or self.note
|
||||
template = self.sale_order_template_id.with_context(lang=self.partner_id.lang)
|
||||
self.note = template.note or self.note
|
||||
|
||||
def _compute_line_data_for_template_change(self, line):
|
||||
return {
|
||||
|
||||
@@ -18,7 +18,7 @@ class SaleOrderTemplate(models.Model):
|
||||
|
||||
name = fields.Char('Quotation Template', required=True)
|
||||
sale_order_template_line_ids = fields.One2many('sale.order.template.line', 'sale_order_template_id', 'Lines', copy=True)
|
||||
note = fields.Text('Terms and conditions')
|
||||
note = fields.Text('Terms and conditions', translate=True)
|
||||
sale_order_template_option_ids = fields.One2many('sale.order.template.option', 'sale_order_template_id', 'Optional Products', copy=True)
|
||||
number_of_days = fields.Integer('Quotation Duration',
|
||||
help='Number of days for the validity date computation of the quotation')
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
<xpath expr="//section[@id='details']//table[@id='sales_order_table']/thead/tr" position="inside">
|
||||
<th t-if="display_remove">
|
||||
<!-- TODO: remove in master -->
|
||||
<t t-set="colspan" t-value="colspan+1"/>
|
||||
</th>
|
||||
</xpath>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<t t-foreach="docs" t-as="o">
|
||||
<t t-call="web.external_layout">
|
||||
<div class="page">
|
||||
<br/>
|
||||
<h2>Stock Inventory</h2>
|
||||
|
||||
<table class="table table-sm">
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
<div name="standard_price_div" class="row ml-0">
|
||||
<field name="standard_price"
|
||||
attrs="{'readonly':[('valuation','=','real_time')]}"
|
||||
widget='monetary' options="{'currency_field': 'currency_id', 'field_digits': True}" class="float-left"/>
|
||||
widget='monetary' options="{'currency_field': 'cost_currency_id', 'field_digits': True}" class="float-left"/>
|
||||
<div name="update_cost_price">
|
||||
<button string="Update Cost" type="action"
|
||||
name="%(action_view_change_standard_price)d"
|
||||
|
||||
@@ -95,6 +95,9 @@ if(!the_form.length) {
|
||||
}
|
||||
|
||||
// checkboxes and radios
|
||||
the_form.find("input[name^='" + key + "_'][type='checkbox']").each(function(){
|
||||
$(this).val(value);
|
||||
});
|
||||
the_form.find("input[name=" + key + "][type!='text']").each(function(){
|
||||
$(this).val(value);
|
||||
});
|
||||
|
||||
@@ -428,6 +428,18 @@ ListRenderer.include({
|
||||
});
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
updateState: function (state, params) {
|
||||
if (params.noRender) {
|
||||
// the state changed, but we won't do a re-rendering right now, so
|
||||
// remove computed modifiers data (as they are obsolete) to force
|
||||
// them to be recomputed at next (sub-)rendering
|
||||
this.allModifiersData = [];
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
@@ -478,7 +490,8 @@ ListRenderer.include({
|
||||
*/
|
||||
_moveToPreviousLine: function () {
|
||||
if (this.currentRow > 0) {
|
||||
this._selectCell(this.currentRow - 1, this.columns.length - 1);
|
||||
// FORWARD-PORT THIS COMMIT UP TO SAAS-12.2
|
||||
this._selectCell(this.currentRow - 1, this.columns.length - 1, {inc: -1});
|
||||
} else {
|
||||
this.unselectRow().then(this.trigger_up.bind(this, 'add_record'));
|
||||
}
|
||||
@@ -692,6 +705,8 @@ ListRenderer.include({
|
||||
* @param {boolean} [options.force=false] if true, force selecting the cell
|
||||
* even if seems to be already the selected one (useful after a re-
|
||||
* rendering, to reset the focus on the correct field)
|
||||
* @param {integer} [options.inc=1] the increment to use when searching for
|
||||
* the "next" possible cell (if the cell to select can't be selected)
|
||||
* @return {Deferred} fails if no cell could be selected
|
||||
*/
|
||||
_selectCell: function (rowIndex, fieldIndex, options) {
|
||||
@@ -715,7 +730,7 @@ ListRenderer.include({
|
||||
var oldFieldIndex = self.currentFieldIndex;
|
||||
self.currentFieldIndex = fieldIndex;
|
||||
fieldIndex = self._activateFieldWidget(record, fieldIndex, {
|
||||
inc: 1,
|
||||
inc: options.inc || 1,
|
||||
wrap: wrap,
|
||||
event: options && options.event,
|
||||
});
|
||||
@@ -868,7 +883,7 @@ ListRenderer.include({
|
||||
switch (ev.data.direction) {
|
||||
case 'previous':
|
||||
if (this.currentFieldIndex > 0) {
|
||||
this._selectCell(this.currentRow, this.currentFieldIndex - 1, {wrap: false})
|
||||
this._selectCell(this.currentRow, this.currentFieldIndex - 1, {inc: -1, wrap: false})
|
||||
.fail(this._moveToPreviousLine.bind(this));
|
||||
} else {
|
||||
this._moveToPreviousLine();
|
||||
|
||||
@@ -2879,6 +2879,87 @@ QUnit.module('Views', {
|
||||
list.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('pressing SHIFT-TAB in editable list with a readonly field [REQUIRE FOCUS]', function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
var list = createView({
|
||||
View: ListView,
|
||||
model: 'foo',
|
||||
data: this.data,
|
||||
arch: '<tree editable="bottom">' +
|
||||
'<field name="foo"/>' +
|
||||
'<field name="int_field" readonly="1"/>' +
|
||||
'<field name="qux"/>' +
|
||||
'</tree>',
|
||||
});
|
||||
|
||||
// start on 'qux', line 3
|
||||
testUtils.dom.click(list.$('.o_data_row:nth(2) .o_data_cell:nth(2)'));
|
||||
assert.hasClass(list.$('.o_data_row:nth(2)'), 'o_selected_row');
|
||||
assert.strictEqual(document.activeElement, list.$('.o_data_row:nth(2) .o_data_cell input[name=qux]')[0]);
|
||||
|
||||
// Press 'shift-Tab' -> should go to first cell (same line)
|
||||
$(document.activeElement).trigger({type: 'keydown', which: $.ui.keyCode.TAB, shiftKey: true});
|
||||
assert.hasClass(list.$('.o_data_row:nth(2)'), 'o_selected_row');
|
||||
assert.strictEqual(document.activeElement, list.$('.o_data_row:nth(2) .o_data_cell input[name=foo]')[0]);
|
||||
|
||||
list.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('pressing SHIFT-TAB in editable list with a readonly field in first column [REQUIRE FOCUS]', function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
var list = createView({
|
||||
View: ListView,
|
||||
model: 'foo',
|
||||
data: this.data,
|
||||
arch: '<tree editable="bottom">' +
|
||||
'<field name="int_field" readonly="1"/>' +
|
||||
'<field name="foo"/>' +
|
||||
'<field name="qux"/>' +
|
||||
'</tree>',
|
||||
});
|
||||
|
||||
// start on 'foo', line 3
|
||||
testUtils.dom.click(list.$('.o_data_row:nth(2) .o_data_cell:nth(1)'));
|
||||
assert.hasClass(list.$('.o_data_row:nth(2)'), 'o_selected_row');
|
||||
assert.strictEqual(document.activeElement, list.$('.o_data_row:nth(2) .o_data_cell input[name=foo]')[0]);
|
||||
|
||||
// Press 'shift-Tab' -> should go to previous line (last cell)
|
||||
$(document.activeElement).trigger({type: 'keydown', which: $.ui.keyCode.TAB, shiftKey: true});
|
||||
assert.hasClass(list.$('.o_data_row:nth(1)'), 'o_selected_row');
|
||||
assert.strictEqual(document.activeElement, list.$('.o_data_row:nth(1) .o_data_cell input[name=qux]')[0]);
|
||||
|
||||
list.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('pressing SHIFT-TAB in editable list with a readonly field in last column [REQUIRE FOCUS]', function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
var list = createView({
|
||||
View: ListView,
|
||||
model: 'foo',
|
||||
data: this.data,
|
||||
arch: '<tree editable="bottom">' +
|
||||
'<field name="int_field"/>' +
|
||||
'<field name="foo"/>' +
|
||||
'<field name="qux" readonly="1"/>' +
|
||||
'</tree>',
|
||||
});
|
||||
|
||||
// start on 'int_field', line 3
|
||||
testUtils.dom.click(list.$('.o_data_row:nth(2) .o_data_cell:first'));
|
||||
assert.hasClass(list.$('.o_data_row:nth(2)'), 'o_selected_row');
|
||||
assert.strictEqual(document.activeElement, list.$('.o_data_row:nth(2) .o_data_cell input[name=int_field]')[0]);
|
||||
|
||||
// Press 'shift-Tab' -> should go to previous line ('foo' field)
|
||||
$(document.activeElement).trigger({type: 'keydown', which: $.ui.keyCode.TAB, shiftKey: true});
|
||||
assert.hasClass(list.$('.o_data_row:nth(1)'), 'o_selected_row');
|
||||
assert.strictEqual(document.activeElement, list.$('.o_data_row:nth(1) .o_data_cell input[name=foo]')[0]);
|
||||
|
||||
list.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('skip invisible fields when navigating list view with TAB', function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
@@ -4097,6 +4178,47 @@ QUnit.module('Views', {
|
||||
|
||||
list.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('create record on list with modifiers depending on id', function (assert) {
|
||||
assert.expect(8);
|
||||
|
||||
var list = createView({
|
||||
View: ListView,
|
||||
model: 'foo',
|
||||
data: this.data,
|
||||
arch: '<tree editable="top">' +
|
||||
'<field name="id" invisible="1"/>' +
|
||||
'<field name="foo" attrs="{\'readonly\': [[\'id\',\'!=\',False]]}"/>' +
|
||||
'<field name="int_field" attrs="{\'invisible\': [[\'id\',\'!=\',False]]}"/>' +
|
||||
'</tree>',
|
||||
});
|
||||
|
||||
// add a new record
|
||||
testUtils.dom.click(list.$buttons.find('.o_list_button_add'));
|
||||
|
||||
// modifiers should be evaluted to false
|
||||
assert.containsOnce(list, '.o_selected_row');
|
||||
assert.doesNotHaveClass(list.$('.o_selected_row .o_data_cell:first'), 'o_readonly_modifier');
|
||||
assert.doesNotHaveClass(list.$('.o_selected_row .o_data_cell:nth(1)'), 'o_invisible_modifier');
|
||||
|
||||
// set a value and save
|
||||
testUtils.fields.editInput(list.$('.o_selected_row input[name=foo]'), 'some value');
|
||||
testUtils.dom.click(list.$buttons.find('.o_list_button_save'));
|
||||
|
||||
// modifiers should be evaluted to true
|
||||
assert.hasClass(list.$('.o_data_row:first .o_data_cell:first'), 'o_readonly_modifier');
|
||||
assert.hasClass(list.$('.o_data_row:first .o_data_cell:nth(1)'), 'o_invisible_modifier');
|
||||
|
||||
// edit again the just created record
|
||||
testUtils.dom.click(list.$('.o_data_row:first .o_data_cell:first'));
|
||||
|
||||
// modifiers should be evaluted to true
|
||||
assert.containsOnce(list, '.o_selected_row');
|
||||
assert.hasClass(list.$('.o_selected_row .o_data_cell:first'), 'o_readonly_modifier');
|
||||
assert.hasClass(list.$('.o_selected_row .o_data_cell:nth(1)'), 'o_invisible_modifier');
|
||||
|
||||
list.destroy();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -115,7 +115,7 @@ class Currency(models.Model):
|
||||
fractional_value = int(parts[2] or 0)
|
||||
|
||||
lang_code = self.env.context.get('lang') or self.env.user.lang
|
||||
lang = self.env['res.lang'].search([('code', '=', lang_code)])
|
||||
lang = self.env['res.lang'].with_context(active_test=False).search([('code', '=', lang_code)])
|
||||
amount_words = tools.ustr('{amt_value} {amt_word}').format(
|
||||
amt_value=_num2words(integer_value, lang=lang.iso_code),
|
||||
amt_word=self.currency_unit_label,
|
||||
|
||||
Reference in New Issue
Block a user