diff --git a/addons/account/models/account_payment.py b/addons/account/models/account_payment.py index 8764d190991..d9e28bff592 100644 --- a/addons/account/models/account_payment.py +++ b/addons/account/models/account_payment.py @@ -510,6 +510,7 @@ class account_payment(models.Model): (transfer_credit_aml + transfer_debit_aml).reconcile() rec.write({'state': 'posted', 'move_name': move.name}) + return True @api.multi def action_draft(self): @@ -524,7 +525,7 @@ class account_payment(models.Model): if any(len(record.invoice_ids) != 1 for record in self): # For multiple invoices, there is account.register.payments wizard raise UserError(_("This method should only be called to process a single invoice's payment.")) - self.post(); + return self.post() def _create_payment_entry(self, amount): """ Create a journal entry corresponding to a payment, if the payment references invoice(s) they are reconciled. diff --git a/addons/crm/views/res_config_settings_views.xml b/addons/crm/views/res_config_settings_views.xml index e06b7c9f7ec..43c7207d5d1 100644 --- a/addons/crm/views/res_config_settings_views.xml +++ b/addons/crm/views/res_config_settings_views.xml @@ -61,7 +61,7 @@
diff --git a/addons/crm_phone_validation/__manifest__.py b/addons/crm_phone_validation/__manifest__.py index 72a21dbfcfa..7c729bfae3e 100644 --- a/addons/crm_phone_validation/__manifest__.py +++ b/addons/crm_phone_validation/__manifest__.py @@ -13,7 +13,6 @@ CRM Phone Numbers Validation This module allows for validate and format phone numbers for leads and contacts.""", 'data': [ 'views/res_config_settings_views.xml', - 'views/crm_lead_views.xml' ], 'depends': [ 'phone_validation', diff --git a/addons/crm_phone_validation/views/crm_lead_views.xml b/addons/crm_phone_validation/views/crm_lead_views.xml deleted file mode 100644 index bcf1840bd7a..00000000000 --- a/addons/crm_phone_validation/views/crm_lead_views.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - crm.lead.form.opportunity.inherit.crm.phone.validation - crm.lead - - - - - - - - diff --git a/addons/l10n_fr_hr_payroll/report/report_l10n_fr_fiche_paye.xml b/addons/l10n_fr_hr_payroll/report/report_l10n_fr_fiche_paye.xml index 56a7e28210e..c9fcc8ae3fd 100644 --- a/addons/l10n_fr_hr_payroll/report/report_l10n_fr_fiche_paye.xml +++ b/addons/l10n_fr_hr_payroll/report/report_l10n_fr_fiche_paye.xml @@ -135,7 +135,7 @@ Net Imposable Plafond s.s. - Coût Global Salarié + Total versé par l'employeur diff --git a/addons/phone_validation/models/phone_validation_mixin.py b/addons/phone_validation/models/phone_validation_mixin.py index 4cd9d8eb2bb..02e4c6aa82a 100644 --- a/addons/phone_validation/models/phone_validation_mixin.py +++ b/addons/phone_validation/models/phone_validation_mixin.py @@ -9,11 +9,12 @@ class PhoneValidationMixin(models.AbstractModel): _name = 'phone.validation.mixin' def _phone_get_country(self): - if 'country_id' in self: + if 'country_id' in self and self.country_id: return self.country_id + return self.env.user.company_id.country_id def _phone_get_always_international(self): - if 'company_id' in self and self.country_id: + if 'company_id' in self and self.company_id: return self.company_id.phone_international_format == 'prefix' return self.env.user.company_id.phone_international_format == 'prefix' diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py index 080d7277657..a97fce8135d 100644 --- a/addons/point_of_sale/models/pos_order.py +++ b/addons/point_of_sale/models/pos_order.py @@ -227,6 +227,12 @@ class PosOrder(models.Model): return InvoiceLine.sudo().create(inv_line) def _create_account_move_line(self, session=None, move=None): + def _flatten_tax_and_children(taxes): + children = self.env['account.tax'] + for tax in taxes: + children |= _flatten_tax_and_children(tax.children_tax_ids) + return taxes + children + # Tricky, via the workflow, we only have one id in the ids variable """Create a account move line of order grouped by products or not.""" IrProperty = self.env['ir.property'] @@ -303,6 +309,9 @@ class PosOrder(models.Model): name = name + ' (' + line.notice + ')' # Create a move for the line for the order line + # Just like for invoices, a group of taxes must be present on this base line + # As well as its children + base_line_tax_ids = _flatten_tax_and_children(line.tax_ids_after_fiscal_position).filtered(lambda tax: tax.type_tax_use in ['sale', 'none']) insert_data('product', { 'name': name, 'quantity': line.qty, @@ -311,7 +320,7 @@ class PosOrder(models.Model): 'analytic_account_id': self._prepare_analytic_account(line), 'credit': ((amount > 0) and amount) or 0.0, 'debit': ((amount < 0) and -amount) or 0.0, - 'tax_ids': [(6, 0, line.tax_ids_after_fiscal_position.ids)], + 'tax_ids': [(6, 0, base_line_tax_ids.ids)], 'partner_id': partner_id }) diff --git a/addons/stock/tests/test_move2.py b/addons/stock/tests/test_move2.py index 45e898856b7..5f1524efa70 100644 --- a/addons/stock/tests/test_move2.py +++ b/addons/stock/tests/test_move2.py @@ -1646,19 +1646,15 @@ class TestSinglePicking(TestStockCommon): class TestStockUOM(TestStockCommon): def setUp(self): - with registry().cursor() as cr: - env = api.Environment(cr, 1, {}) - dp = env.ref('product.decimal_product_uom') - self.old_digits = dp.digits - dp.digits = 7 super(TestStockUOM, self).setUp() + dp = self.env.ref('product.decimal_product_uom') + dp.digits = 7 - def tearDown(self): - super(TestStockUOM, self).tearDown() - with self.registry.cursor() as cr: - env = api.Environment(cr, 1, {}) - dp = env.ref('product.decimal_product_uom') - dp.digits = self.old_digits + # Trick: invoke the method 'precision_get' with the current environment. + # This fills in the cache of the method with the right value. If we + # don't do that, the registry will access the corresponding precision + # with a new cursor (LazyCursor), and get a different value! + self.assertEqual(dp.precision_get(dp.name), 7) def test_pickings_transfer_with_different_uom_and_back_orders(self): """ Picking transfer with diffrent unit of meassure. """ diff --git a/addons/web/static/src/js/views/basic/basic_renderer.js b/addons/web/static/src/js/views/basic/basic_renderer.js index 58a5cb7a70e..8db747f308d 100644 --- a/addons/web/static/src/js/views/basic/basic_renderer.js +++ b/addons/web/static/src/js/views/basic/basic_renderer.js @@ -30,6 +30,7 @@ var BasicRenderer = AbstractRenderer.extend({ this.activeActions = params.activeActions; this.viewType = params.viewType; this.mode = params.mode || 'readonly'; + this.widgets = []; }, /** * This method has two responsabilities: find every invalid fields in the @@ -95,6 +96,9 @@ var BasicRenderer = AbstractRenderer.extend({ return this._render().then(_.constant([])); } + // reset all widgets (from the tag) if any: + _.invoke(this.widgets, 'updateState', state); + var defs = []; // Reset all the field widgets that are marked as changed and the ones @@ -613,6 +617,8 @@ var BasicRenderer = AbstractRenderer.extend({ var Widget = widgetRegistry.get(node.attrs.name); var widget = new Widget(this, record); + this.widgets.push(widget); + // Prepare widget rendering and save the related deferred var def = widget._widgetRenderAndInsert(function () {}); if (def.state() === 'pending') { diff --git a/addons/web/static/src/js/views/list/list_renderer.js b/addons/web/static/src/js/views/list/list_renderer.js index f4c5219259a..61871bfc2bd 100644 --- a/addons/web/static/src/js/views/list/list_renderer.js +++ b/addons/web/static/src/js/views/list/list_renderer.js @@ -71,6 +71,7 @@ var ListRenderer = BasicRenderer.extend({ */ updateState: function (state, params) { this._processColumns(params.columnInvisibleFields || {}); + this.selection = []; return this._super.apply(this, arguments); }, @@ -202,9 +203,11 @@ var ListRenderer = BasicRenderer.extend({ var field = self.state.fields[column.attrs.name]; var value = aggregateValues[column.attrs.name].value; var help = aggregateValues[column.attrs.name].help; - var formattedValue = field_utils.format[field.type](value, field, { - escape: true, - }); + var formatFunc = field_utils.format[column.attrs.widget]; + if (!formatFunc) { + formatFunc = field_utils.format[field.type]; + } + var formattedValue = formatFunc(value, field, {escape: true}); $cell.addClass('o_list_number').attr('title', help).html(formattedValue); } return $cell; diff --git a/addons/web/static/tests/views/form_tests.js b/addons/web/static/tests/views/form_tests.js index d2ec320c6ea..7675e21bb45 100644 --- a/addons/web/static/tests/views/form_tests.js +++ b/addons/web/static/tests/views/form_tests.js @@ -6232,6 +6232,41 @@ QUnit.module('Views', { delete widgetRegistry.map.test; }); + QUnit.test('basic support for widgets', function (assert) { + assert.expect(1); + + var MyWidget = Widget.extend({ + init: function (parent, dataPoint) { + this.data = dataPoint.data; + }, + start: function () { + this.$el.text(this.data.foo + "!"); + }, + updateState: function (dataPoint) { + this.$el.text(dataPoint.data.foo + "!"); + }, + }); + widgetRegistry.add('test', MyWidget); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '', + }); + + form.$('input[name="foo"]').val("I am alive").trigger('input'); + assert.strictEqual(form.$('.o_widget').text(), 'I am alive!', + "widget should have been updated"); + + form.destroy(); + delete widgetRegistry.map.test; + }); + + QUnit.test('bounce edit button in readonly mode', function (assert) { assert.expect(3); diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js index 2fafef26c6f..92ae4033562 100644 --- a/addons/web/static/tests/views/list_tests.js +++ b/addons/web/static/tests/views/list_tests.js @@ -582,6 +582,40 @@ QUnit.module('Views', { list.destroy(); }); + QUnit.test('selection is reset on reload', function (assert) { + assert.expect(5); + + var list = createView({ + View: ListView, + model: 'foo', + data: this.data, + arch: '' + + '' + + '' + + '', + }); + + assert.strictEqual(list.$('tfoot td:nth(2)').text(), '32', + "total should be 32 (no record selected)"); + + // select first record + var $firstRowSelector = list.$('tbody .o_list_record_selector input').first(); + $firstRowSelector.click(); + assert.ok($firstRowSelector.is(':checked'), "first row should be selected"); + assert.strictEqual(list.$('tfoot td:nth(2)').text(), '10', + "total should be 10 (first record selected)"); + + // reload + list.reload(); + $firstRowSelector = list.$('tbody .o_list_record_selector input').first(); + assert.notOk($firstRowSelector.is(':checked'), + "first row should no longer be selected"); + assert.strictEqual(list.$('tfoot td:nth(2)').text(), '32', + "total should be 32 (no more record selected)"); + + list.destroy(); + }); + QUnit.test('aggregates are computed correctly', function (assert) { assert.expect(4); @@ -660,6 +694,25 @@ QUnit.module('Views', { list.destroy(); }); + QUnit.test('aggregates are formatted according to field widget', function (assert) { + assert.expect(1); + + var list = createView({ + View: ListView, + model: 'foo', + data: this.data, + arch: '' + + '' + + '' + + '', + }); + + assert.strictEqual(list.$('tfoot td:nth(2)').text(), '19:24', + "total should be formatted as a float_time"); + + list.destroy(); + }); + QUnit.test('groups can be sorted on aggregates', function (assert) { assert.expect(10); diff --git a/addons/web_editor/static/src/js/widgets/ace.js b/addons/web_editor/static/src/js/widgets/ace.js index 6c28c1e2332..0a89d0d8ce7 100644 --- a/addons/web_editor/static/src/js/widgets/ace.js +++ b/addons/web_editor/static/src/js/widgets/ace.js @@ -742,7 +742,7 @@ var ViewEditor = Widget.extend({ var text = data.text || ''; if (!isSelected) { - text = Array($elem.data('level') + 1).join('-') + ' ' + text; + text = Array(($elem.data('level') || 0) + 1).join('-') + ' ' + text; } var $div = $('
', { text: text, diff --git a/addons/website_crm_phone_validation/controllers/website_form.py b/addons/website_crm_phone_validation/controllers/website_form.py index 3713fe86f9e..c404bb87724 100644 --- a/addons/website_crm_phone_validation/controllers/website_form.py +++ b/addons/website_crm_phone_validation/controllers/website_form.py @@ -4,8 +4,6 @@ from odoo.addons.website_form.controllers.main import WebsiteForm from odoo.http import request, route -import json - class WebsiteForm(WebsiteForm): @@ -32,17 +30,13 @@ class WebsiteForm(WebsiteForm): else: record = data.get('record', {}) phone_fields = self._get_phone_fields_to_validate() - country = self._get_country() - invalid_fields = {} + country = request.env['res.country'].browse(record.get('country_id')) + contact_country = country.exists() and country or self._get_country() for phone_field in phone_fields: if not record.get(phone_field): continue number = record[phone_field] - try: - request.env[model_name].phone_format(number, country) - except Exception as error: - invalid_fields[phone_field] = error.args[0] - if invalid_fields: - return json.dumps({'error_fields': invalid_fields}) + fmt_number = request.env[model_name].phone_format(number, contact_country) + request.params.update({phone_field: fmt_number}) return super(WebsiteForm, self).website_form(model_name, **kwargs)