[MERGE] forward port branch 11.0 up to 7e33b54771

This commit is contained in:
Christophe Simonis
2019-03-18 16:51:57 +01:00
26 changed files with 360 additions and 68 deletions
+14 -6
View File
@@ -503,16 +503,19 @@ class AccountInvoice(models.Model):
"""
res = super(AccountInvoice, self).default_get(default_fields)
if not res.get('type', False) == 'out_invoice' or not 'company_id' in res:
if res.get('type', False) not in ('out_invoice', 'in_refund') or not 'company_id' in res:
return res
company = self.env['res.company'].browse(res['company_id'])
if company.partner_id:
partner_bank_result = self.env['res.partner.bank'].search([('partner_id', '=', company.partner_id.id)], limit=1)
if partner_bank_result:
res['partner_bank_id'] = partner_bank_result.id
partner_bank_result = self._get_partner_bank_id(res['company_id'])
if partner_bank_result:
res['partner_bank_id'] = partner_bank_result.id
return res
def _get_partner_bank_id(self, company_id):
company = self.env['res.company'].browse(company_id)
if company.partner_id:
return self.env['res.partner.bank'].search([('partner_id', '=', company.partner_id.id)], limit=1)
@api.model
def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False):
def get_view_id(xid, name):
@@ -1369,6 +1372,11 @@ class AccountInvoice(models.Model):
values['payment_term_id'] = False
values['refund_invoice_id'] = invoice.id
if values['type'] == 'in_refund':
partner_bank_result = self._get_partner_bank_id(values['company_id'])
if partner_bank_result:
values['partner_bank_id'] = partner_bank_result.id
if date:
values['date'] = date
if description:
+2 -1
View File
@@ -143,8 +143,9 @@ class AccountMove(models.Model):
def post(self):
invoice = self._context.get('invoice', False)
self._post_validate()
# Create the analytic lines in batch is faster as it leads to less cache invalidation.
self.mapped('line_ids').create_analytic_lines()
for move in self:
move.line_ids.create_analytic_lines()
if move.name == '/':
new_name = False
journal = move.journal_id
@@ -108,3 +108,39 @@ class TestAccountSupplierInvoice(AccountingTestCase):
#I cancel the account move which is in posted state and verifies that it gives warning message
with self.assertRaises(Warning):
invoice.move_id.button_cancel()
def test_vendor_bill_refund(self):
invoice_account = self.env['account.account'].search(
[('user_type_id', '=', self.env.ref('account.data_account_type_receivable').id)], limit=1)
invoice_line_account = self.env['account.account'].search(
[('user_type_id', '=', self.env.ref('account.data_account_type_expenses').id)], limit=1)
if self.env.ref('base.main_partner').bank_account_count > 0:
bank = self.env['res.partner.bank'].search([('partner_id', '=', self.env.ref('base.main_partner').id)], limit=1)
else:
bank = self.env['res.partner.bank'].create({
'acc_number': '12345678910',
'partner_id': self.env.ref('base.main_partner').id,
})
invoice_id = self.env['account.invoice'].create({
'name': 'invoice test refund',
'reference_type': 'none',
'partner_id': self.env.ref("base.res_partner_2").id,
'account_id': invoice_account.id,
'currency_id': self.env.ref('base.USD').id,
'type': 'in_invoice',
})
self.env['account.invoice.line'].create({
'product_id': self.env.ref("product.product_product_4").id,
'quantity': 1,
'price_unit': 15.0,
'invoice_id': invoice_id.id,
'name': 'something',
'account_id': invoice_line_account.id,
})
refund_invoices = invoice_id.refund()
self.assertEqual(refund_invoices.partner_bank_id, bank)
+1 -3
View File
@@ -18,7 +18,5 @@ class AccountMove(models.Model):
@api.multi
def post(self):
for move in self:
for depreciation_line in move.asset_depreciation_ids:
depreciation_line.post_lines_and_close_asset()
self.mapped('asset_depreciation_ids').post_lines_and_close_asset()
return super(AccountMove, self).post()
+24 -5
View File
@@ -500,8 +500,12 @@ class AccountAssetDepreciationLine(models.Model):
@api.multi
def create_move(self, post_move=True):
created_moves = self.env['account.move']
# `line.move_id` was invalidated from the cache at each iteration
# To prevent to refetch `move_id` of all lines at each iteration just to check a UserError,
# we use an intermediar dict which stores the information the UserError check requires.
line_moves = {line: line.move_id for line in self}
for line in self:
if line.move_id:
if line_moves[line]:
raise UserError(_('This depreciation is already linked to a journal entry! Please post or delete it.'))
move_vals = self._prepare_move(line)
move = self.env['account.move'].create(move_vals)
@@ -547,6 +551,7 @@ class AccountAssetDepreciationLine(models.Model):
'line_ids': [(0, 0, move_line_1), (0, 0, move_line_2)],
}
return move_vals
line_moves[line] = move
def _prepare_move_grouped(self):
category_id = self[0].asset_id.category_id # we can suppose that all lines have the same category
@@ -602,12 +607,19 @@ class AccountAssetDepreciationLine(models.Model):
@api.multi
def post_lines_and_close_asset(self):
# we re-evaluate the assets to determine whether we can close them
# `message_post` invalidates the (whole) cache
# preprocess the assets and lines in which a message should be posted,
# and then post in batch will prevent the re-fetch of the same data over and over.
assets_to_close = self.env['account.asset.asset']
for line in self:
line.log_message_when_posted()
asset = line.asset_id
if asset.currency_id.is_zero(asset.value_residual):
asset.message_post(body=_("Document closed."))
asset.write({'state': 'close'})
assets_to_close |= asset
self.log_message_when_posted()
assets_to_close.write({'state': 'close'})
for asset in assets_to_close:
asset.message_post(body=_("Document closed."))
@api.multi
def log_message_when_posted(self):
@@ -620,6 +632,10 @@ class AccountAssetDepreciationLine(models.Model):
message += '%s</div>' % values
return message
# `message_post` invalidates the (whole) cache
# preprocess the assets in which messages should be posted,
# and then post in batch will prevent the re-fetch of the same data over and over.
assets_to_post = {}
for line in self:
if line.move_id and line.move_id.state == 'draft':
partner_name = line.asset_id.partner_id.name
@@ -628,7 +644,10 @@ class AccountAssetDepreciationLine(models.Model):
if partner_name:
msg_values[_('Partner')] = partner_name
msg = _format_message(_('Depreciation line posted.'), msg_values)
line.asset_id.message_post(body=msg)
assets_to_post.setdefault(line.asset_id, []).append(msg)
for asset, messages in assets_to_post.items():
for msg in messages:
asset.message_post(body=msg)
@api.multi
def unlink(self):
+17 -20
View File
@@ -56,20 +56,18 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, {
this.isChromeMobile = isMobile && navigator.userAgent.match(/Chrome/i);
// Creates an input who will receive the barcode scanner value.
if (this.isChromeMobile) {
this.$barcodeInput = $('<input/>', {
name: 'barcode',
type: 'text',
css: {
'position': 'fixed',
'top': '50%',
'transform': 'translateY(-50%)',
'z-index': '-1',
},
});
// Avoid to show autocomplete for a non appearing input
this.$barcodeInput.attr('autocomplete', 'off');
}
this.$barcodeInput = $('<input/>', {
name: 'barcode',
type: 'text',
css: {
'position': 'fixed',
'top': '50%',
'transform': 'translateY(-50%)',
'z-index': '-1',
},
});
// Avoid to show autocomplete for a non appearing input
this.$barcodeInput.attr('autocomplete', 'off');
this.__blurBarcodeInput = _.debounce(this._blurBarcodeInput, this.inputTimeOut);
},
@@ -220,7 +218,8 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, {
* @param {jQuery.Event} e keydown event
*/
_listenBarcodeScanner: function (e) {
if (!$('input:text:focus, textarea:focus, [contenteditable]:focus').length) {
if ($(document.activeElement).not('input:text, textarea, [contenteditable], ' +
'[type="email"], [type="number"], [type="password"], [type="tel"]').length) {
$('body').append(this.$barcodeInput);
this.$barcodeInput.focus();
}
@@ -263,11 +262,9 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, {
* @private
*/
_blurBarcodeInput: function () {
if (this.$barcodeInput) {
// Close the virtual keyboard on mobile browsers
// FIXME: actually we can't prevent keyboard from opening
this.$barcodeInput.val('').blur();
}
// Close the virtual keyboard on mobile browsers
// FIXME: actually we can't prevent keyboard from opening
this.$barcodeInput.val('').blur();
},
start: function(prevent_key_repeat){
@@ -0,0 +1,74 @@
odoo.define('barcodes.barcode_mobile_tests', function () {
"use strict";
QUnit.module('Barcodes', {}, function () {
QUnit.module('Barcodes Mobile');
QUnit.test('barcode field automatically focus behavior', function (assert) {
assert.expect(10);
// Mock Chrome mobile environment
var barcodeEvents = odoo.__DEBUG__.services["barcodes.BarcodeEvents"].BarcodeEvents;
var __isChromeMobile = barcodeEvents.isChromeMobile;
barcodeEvents.isChromeMobile = true;
// Rebind keyboard events
barcodeEvents.stop();
barcodeEvents.start();
var $form = $(
'<form>' +
'<input name="email" type="email"/>' +
'<input name="number" type="number"/>' +
'<input name="password" type="password"/>' +
'<input name="tel" type="tel"/>' +
'<input name="text"/>' +
'<input name="explicit_text" type="text"/>' +
'<textarea></textarea>' +
'<div contenteditable="true"></div>' +
'<select name="select">' +
'<option value="option1">Option 1</option>' +
'<option value="option2">Option 2</option>' +
'</select>' +
'</form>');
$('#qunit-fixture').append($form);
// Some elements doesn't need to keep the focus
$('body').keydown();
assert.strictEqual(document.activeElement.name, 'barcode',
"hidden barcode input should have the focus");
var $element = $form.find('select');
$element.focus().keydown();
assert.strictEqual(document.activeElement.name, 'barcode',
"hidden barcode input should have the focus");
// Those elements absolutely need to keep the focus:
// inputs elements:
var keepFocusedElements = ['email', 'number', 'password', 'tel',
'text', 'explicit_text'];
for (var i = 0; i < keepFocusedElements.length; ++i) {
$element = $form.find('input[name=' + keepFocusedElements[i] + ']');
$element.focus().keydown();
assert.strictEqual(document.activeElement, $element[0],
"input " + keepFocusedElements[i] + " should keep focus");
}
// textarea element
$element = $form.find('textarea');
$element.focus().keydown();
assert.strictEqual(document.activeElement, $element[0],
"textarea should keep focus");
// contenteditable elements
$element = $form.find('[contenteditable=true]');
$element.focus().keydown();
assert.strictEqual(document.activeElement, $element[0],
"contenteditable should keep focus");
$('#qunit-fixture').empty();
barcodeEvents.isChromeMobile = __isChromeMobile;
// Rebind keyboard events
barcodeEvents.stop();
barcodeEvents.start();
});
});
});
@@ -16,4 +16,10 @@
</xpath>
</template>
<template id="qunit_mobile_suite" name="barcode_mobile_tests" inherit_id="web.qunit_mobile_suite">
<xpath expr="//t[@t-set='head']" position="inside">
<script type="text/javascript" src="/barcodes/static/tests/barcode_mobile_tests.js"></script>
</xpath>
</template>
</odoo>
+1 -1
View File
@@ -150,7 +150,7 @@ class HardwareScreen(web.Home):
</head>
<body class="original_body">
<div hidden class="shadow"></div>
<div class="container">
<div class="container-fluid">
<div class="row">
<div class="col-md-4 col-md-offset-4">
<h1>Odoo Point of Sale</h1>
+2 -2
View File
@@ -32,8 +32,8 @@
$("head").append($parsedHTML.find(".resources").html());
}
$(".container").html($parsedHTML.find('.pos-customer_facing_display').html());
$(".container").attr('class', 'container').addClass($parsedHTML.find('.pos-customer_facing_display').attr('class'));
$(".container-fluid").html($parsedHTML.find('.pos-customer_facing_display').html());
$(".container-fluid").attr('class', 'container-fluid').addClass($parsedHTML.find('.pos-customer_facing_display').attr('class'));
var d = $('.pos_orderlines_list');
d.scrollTop(d.prop("scrollHeight"));
+3 -1
View File
@@ -223,11 +223,13 @@ class XmlDeclaration(models.TransientModel):
if dispatchmode:
mode1 = 'out_invoice'
mode2 = 'in_refund'
extrf = "29"
declcode = self._get_expedition_code(extendedmode)
declform = self._get_expedition_form(extendedmode)
else:
mode1 = 'in_invoice'
mode2 = 'out_refund'
extrf = "19"
declcode = self._get_reception_code(extendedmode)
declform = self._get_reception_form(extendedmode)
@@ -272,7 +274,7 @@ class XmlDeclaration(models.TransientModel):
invoicelines = self.env['account.invoice.line'].browse(invoicelines_ids)
for inv_line in invoicelines:
linekey = self._get_intrastat_linekey(declcode, inv_line, dispatchmode, extendedmode)
linekey = self._get_intrastat_linekey(extrf, inv_line, dispatchmode, extendedmode)
if linekey is None:
continue
+9 -5
View File
@@ -9,7 +9,7 @@ from datetime import datetime
from odoo import api, fields, models, _
from odoo.exceptions import Warning
from odoo.tools import pycompat, DEFAULT_SERVER_DATE_FORMAT
from odoo.tools import float_is_zero, pycompat, DEFAULT_SERVER_DATE_FORMAT
class AccountFrFec(models.TransientModel):
@@ -183,14 +183,16 @@ class AccountFrFec(models.TransientModel):
sql_query += '''
GROUP BY aml.account_id, aat.type
HAVING sum(aml.balance) != 0
HAVING round(sum(aml.balance), %s) != 0
AND aat.type not in ('receivable', 'payable')
'''
formatted_date_from = self.date_from.replace('-', '')
date_from = datetime.strptime(self.date_from, DEFAULT_SERVER_DATE_FORMAT)
formatted_date_year = date_from.year
currency_digits = 2
self._cr.execute(
sql_query, (formatted_date_year, formatted_date_from, formatted_date_from, formatted_date_from, self.date_from, company.id))
sql_query, (formatted_date_year, formatted_date_from, formatted_date_from, formatted_date_from, self.date_from, company.id, currency_digits))
for row in self._cr.fetchall():
listrow = list(row)
@@ -203,6 +205,8 @@ class AccountFrFec(models.TransientModel):
current_amount = float(listrow[11].replace(',', '.')) - float(listrow[12].replace(',', '.'))
unaffected_earnings_amount = float(unaffected_earnings_results[11].replace(',', '.')) - float(unaffected_earnings_results[12].replace(',', '.'))
listrow_amount = current_amount + unaffected_earnings_amount
if float_is_zero(listrow_amount, precision_digits=currency_digits):
continue
if listrow_amount > 0:
listrow[11] = str(listrow_amount).replace('.', ',')
listrow[12] = '0,00'
@@ -270,11 +274,11 @@ class AccountFrFec(models.TransientModel):
sql_query += '''
GROUP BY aml.account_id, aat.type, rp.ref, rp.id
HAVING sum(aml.balance) != 0
HAVING round(sum(aml.balance), %s) != 0
AND aat.type in ('receivable', 'payable')
'''
self._cr.execute(
sql_query, (formatted_date_year, formatted_date_from, formatted_date_from, formatted_date_from, self.date_from, company.id))
sql_query, (formatted_date_year, formatted_date_from, formatted_date_from, formatted_date_from, self.date_from, company.id, currency_digits))
for row in self._cr.fetchall():
listrow = list(row)
+2
View File
@@ -200,6 +200,7 @@
<field name="tax_exigibility">on_payment</field>
<field name="cash_basis_account" ref="cuenta118_01"/>
<field name="tag_ids" eval="[(6,0,[ref('tag_diot_8')])]"/>
<field name="sequence" eval="10"/>
</record>
<record id="tax17" model="account.tax.template">
@@ -215,6 +216,7 @@
<field name="tax_group_id" ref="tax_group_iva_8"/>
<field name="tax_exigibility">on_payment</field>
<field name="cash_basis_account" ref="cuenta208_01"/>
<field name="sequence" eval="10"/>
</record>
</data>
</odoo>
+2 -1
View File
@@ -368,6 +368,7 @@ class MrpWorkorder(models.Model):
move_line.product_uom_qty += self.qty_producing
move_line.qty_done += self.qty_producing
else:
location_dest_id = production_move.location_dest_id.get_putaway_strategy(self.product_id).id or production_move.location_dest_id.id
move_line.create({'move_id': production_move.id,
'product_id': production_move.product_id.id,
'lot_id': self.final_lot_id.id,
@@ -376,7 +377,7 @@ class MrpWorkorder(models.Model):
'qty_done': self.qty_producing,
'workorder_id': self.id,
'location_id': production_move.location_id.id,
'location_dest_id': production_move.location_dest_id.id,
'location_dest_id': location_dest_id,
})
else:
production_move.quantity_done += self.qty_producing
+112
View File
@@ -6,6 +6,52 @@ from odoo.exceptions import except_orm
class TestWarehouse(common.TestMrpCommon):
def setUp(self):
super(TestWarehouse, self).setUp()
self.stock_location = self.env.ref('stock.stock_location_stock')
self.depot_location = self.env['stock.location'].create({
'name': 'Depot',
'usage': 'internal',
'location_id': self.stock_location.id,
})
putaway = self.env['product.putaway'].create({
'name': 'putaway stock->depot',
'fixed_location_ids': [(0, 0, {
'category_id': self.env.ref('product.product_category_all').id,
'fixed_location_id': self.depot_location.id,
})]
})
self.stock_location.write({
'putaway_strategy_id': putaway.id,
})
self.laptop = self.env.ref("product.product_product_25")
graphics_card = self.env.ref("product.product_product_24")
unit = self.env.ref("product.product_uom_unit")
mrp_routing = self.env.ref("mrp.mrp_routing_0")
bom_laptop = self.env['mrp.bom'].create({
'product_tmpl_id': self.laptop.product_tmpl_id.id,
'product_qty': 1,
'product_uom_id': unit.id,
'bom_line_ids': [(0, 0, {
'product_id': graphics_card.id,
'product_qty': 1,
'product_uom_id': unit.id
})],
'routing_id': mrp_routing.id
})
# Return a new Manufacturing Order for laptop
def new_mo_laptop():
return self.env['mrp.production'].create({
'product_id': self.laptop.id,
'product_qty': 1,
'product_uom_id': unit.id,
'bom_id': bom_laptop.id
})
self.new_mo_laptop = new_mo_laptop
def test_manufacturing_route(self):
warehouse_1_stock_manager = self.warehouse_1.sudo(self.user_stock_manager)
@@ -100,3 +146,69 @@ class TestWarehouse(common.TestMrpCommon):
# scrap_move = production_3.move_raw_ids.filtered(lambda x: x.product_id == self.product_2 and x.scrapped)
# self.assertTrue(scrap_move, "There are no any scrap move created for production order.")
def test_putaway_after_manufacturing_1(self):
""" This test checks a manufactured product without tracking will go to
location defined in putaway strategy.
"""
mo_laptop = self.new_mo_laptop()
mo_laptop.button_plan()
workorder = mo_laptop.workorder_ids[0]
workorder.button_start()
workorder.record_production()
mo_laptop.button_mark_done()
# We check if the laptop go in the depot and not in the stock
move = mo_laptop.move_finished_ids
location_dest = move.move_line_ids.location_dest_id
self.assertEqual(location_dest.id, self.depot_location.id)
self.assertNotEqual(location_dest.id, self.stock_location.id)
def test_putaway_after_manufacturing_2(self):
""" This test checks a tracked manufactured product will go to location
defined in putaway strategy.
"""
self.laptop.tracking = 'serial'
mo_laptop = self.new_mo_laptop()
mo_laptop.button_plan()
workorder = mo_laptop.workorder_ids[0]
workorder.button_start()
serial = self.env['stock.production.lot'].create({'product_id': self.laptop.id})
workorder.final_lot_id = serial
workorder.record_production()
mo_laptop.button_mark_done()
# We check if the laptop go in the depot and not in the stock
move = mo_laptop.move_finished_ids
location_dest = move.move_line_ids.location_dest_id
self.assertEqual(location_dest.id, self.depot_location.id)
self.assertNotEqual(location_dest.id, self.stock_location.id)
def test_putaway_after_manufacturing_3(self):
""" This test checks a tracked manufactured product will go to location
defined in putaway strategy when the production is recorded with
product.produce wizard.
"""
self.laptop.tracking = 'serial'
mo_laptop = self.new_mo_laptop()
serial = self.env['stock.production.lot'].create({'product_id': self.laptop.id})
product_produce = self.env['mrp.product.produce'].with_context({
'active_id': mo_laptop.id,
'active_ids': [mo_laptop.id],
}).create({
'product_qty': 1.0,
'lot_id': serial.id,
})
product_produce.do_produce()
mo_laptop.button_mark_done()
# We check if the laptop go in the depot and not in the stock
move = mo_laptop.move_finished_ids
location_dest = move.move_line_ids.location_dest_id
self.assertEqual(location_dest.id, self.depot_location.id)
self.assertNotEqual(location_dest.id, self.stock_location.id)
+2 -1
View File
@@ -133,6 +133,7 @@ class MrpProductProduce(models.TransientModel):
existing_move_line.product_uom_qty += self.product_qty
existing_move_line.qty_done += self.product_qty
else:
location_dest_id = produce_move.location_dest_id.get_putaway_strategy(self.product_id).id or produce_move.location_dest_id.id
vals = {
'move_id': produce_move.id,
'product_id': produce_move.product_id.id,
@@ -142,7 +143,7 @@ class MrpProductProduce(models.TransientModel):
'qty_done': self.product_qty,
'lot_id': self.lot_id.id,
'location_id': produce_move.location_id.id,
'location_dest_id': produce_move.location_dest_id.id,
'location_dest_id': location_dest_id,
}
self.env['stock.move.line'].create(vals)
+1
View File
@@ -856,6 +856,7 @@ class PosOrder(models.Model):
for pack_lot in pack_lots:
lot_id, qty = pack_lot['lot_id'], pack_lot['qty']
self.env['stock.move.line'].create({
'picking_id': move.picking_id.id,
'move_id': move.id,
'product_id': move.product_id.id,
'product_uom_id': move.product_uom.id,
@@ -35,6 +35,7 @@ body .pos-customer_facing_display {
font-weight: 300;
width: 100%;
height: 100%;
padding: 0;
-webkit-display: flex;
-moz-display: flex;
-ms-display: flex;
@@ -95,6 +95,7 @@ body {
font-weight: 300;
width: 100%;
height: 100%;
padding: 0;
.flex-display();
.flex-direction(row);
+1 -1
View File
@@ -87,7 +87,7 @@ class PortalWizardUser(models.TransientModel):
partners_error_empty |= wizard_user.partner_id
elif email in emails:
partners_error_emails |= wizard_user.partner_id
user = self.env['res.users'].sudo().with_context(active_test=False).search([('login', '=', email)])
user = self.env['res.users'].sudo().with_context(active_test=False).search([('login', '=ilike', email)])
if user:
partners_error_user |= wizard_user.partner_id
emails.append(email)
+4 -3
View File
@@ -79,10 +79,11 @@ class MrpStockReport(models.TransientModel):
res_model = ''
ref = ''
res_id = False
if move_line.picking_id:
picking_id = move_line.picking_id or move_line.move_id.picking_id
if picking_id:
res_model = 'stock.picking'
res_id = move_line.picking_id.id
ref = move_line.picking_id.name
res_id = picking_id.id
ref = picking_id.name
elif move_line.move_id.inventory_id:
res_model = 'stock.inventory'
res_id = move_line.move_id.inventory_id.id
+1 -1
View File
@@ -173,7 +173,7 @@ var QWeb2 = {
new_dict[as_first] = index === 0;
new_dict[as_last] = index + 1 === size;
new_dict[as_parity] = (index % 2 == 1 ? 'odd' : 'even');
if (cur.constructor === Object) {
if (cur && cur.constructor === Object) {
this.extend(new_dict, cur);
}
new_dict[as] = cur;
@@ -346,7 +346,7 @@ var FormRenderer = BasicRenderer.extend({
var visible_buttons = buttons_partition[1];
// Get the unfolded buttons according to window size
var nb_buttons = [2, 2, 4, 6][config.device.size_class] || 7;
var nb_buttons = this._renderButtonBoxNbButtons();
var unfolded_buttons = visible_buttons.slice(0, nb_buttons).concat(invisible_buttons);
// Get the folded buttons
@@ -385,6 +385,13 @@ var FormRenderer = BasicRenderer.extend({
this._registerModifiers(node, this.state, $result);
return $result;
},
/**
* @private
* @returns {integer}
*/
_renderButtonBoxNbButtons: function () {
return [2, 2, 4, 6][config.device.size_class] || 7;
},
/**
* @private
* @param {Object} node
@@ -291,12 +291,10 @@ var PivotModel = AbstractModel.extend({
// if we don't have the key 'groupBy' in params. In that case,
// we want to have the full open state for the groupbys.
self._updateTree(old_row.root, self.data.main_row.root);
self._updateMainGroupBys(old_row, self.data.main_row);
}
if (!('pivot_column_groupby' in (params.context || {}))) {
self._updateTree(old_col.root, self.data.main_col.root);
self._updateMainGroupBys(old_col, self.data.main_col);
}
});
},
@@ -747,19 +745,6 @@ var PivotModel = AbstractModel.extend({
this._traverseTree(root.children[i], f, arg1, arg2, arg3);
}
},
/**
* Updates the groupBys that the main[row | col] holds
*
* @private
* @param {Object} old: The main abstract header before reload
* @param {Object} main: The main abstract header after reload
*
*/
_updateMainGroupBys: function (old, main) {
var new_groupby_length = this._getHeaderDepth(main.root) - 1;
main.root.groupbys = old.root.groupbys;
main.root.groupbys.splice(new_groupby_length);
},
/**
* @param {Object} old_tree
* @param {Object} new_tree
@@ -1036,6 +1036,41 @@ QUnit.module('Views', {
pivot.destroy();
});
QUnit.test('Empty results keep groupbys', function (assert) {
assert.expect(2);
var pivot = createView({
View: PivotView,
model: "partner",
data: this.data,
arch: '<pivot/>',
});
// Set a column groupby
pivot.$('thead .o_pivot_header_cell_closed').click();
pivot.$('.o_field_selection li[data-field=customer] a').click();
// Set a domain for empty results
pivot.update({domain: [['id', '=', false]]});
var expectedContext = {pivot_column_groupby: undefined,
pivot_measures: undefined,
pivot_row_groupby: undefined};
assert.deepEqual(pivot.getContext(), expectedContext,
'Column groupby not lost after empty results');
// Set a domain for not empty results
pivot.update({domain: [['product_id', '=', 37]]});
var expectedContext = {pivot_column_groupby: ['customer'],
pivot_measures: ['__count'],
pivot_row_groupby: []};
assert.deepEqual(pivot.getContext(), expectedContext,
'Column groupby not lost after reload after empty results');
pivot.destroy();
});
QUnit.test('correctly uses pivot_ keys from the context', function (assert) {
assert.expect(7);
+1 -1
View File
@@ -426,7 +426,7 @@ class Partner(models.Model):
as if they were related fields """
commercial_partner = self.commercial_partner_id
if commercial_partner != self:
sync_vals = commercial_partner._update_fields_values(self._commercial_fields())
sync_vals = commercial_partner.with_prefetch()._update_fields_values(self._commercial_fields())
self.write(sync_vals)
@api.multi