diff --git a/addons/account/models/partner.py b/addons/account/models/partner.py index db8c4918b81..e48aa2e3b38 100644 --- a/addons/account/models/partner.py +++ b/addons/account/models/partner.py @@ -268,7 +268,7 @@ class ResPartner(models.Model): all_partner_ids = [] for partner in self: # price_total is in the company currency - all_partners_and_children[partner] = self.search([('id', 'child_of', partner.id)]).ids + all_partners_and_children[partner] = self.with_context(active_test=False).search([('id', 'child_of', partner.id)]).ids all_partner_ids += all_partners_and_children[partner] # searching account.invoice.report via the orm is comparatively expensive diff --git a/addons/account/static/src/js/reconciliation/reconciliation_model.js b/addons/account/static/src/js/reconciliation/reconciliation_model.js index 877dae2c9e0..880619411fe 100644 --- a/addons/account/static/src/js/reconciliation/reconciliation_model.js +++ b/addons/account/static/src/js/reconciliation/reconciliation_model.js @@ -3,6 +3,8 @@ odoo.define('account.ReconciliationModel', function (require) { var BasicModel = require('web.BasicModel'); var field_utils = require('web.field_utils'); +var utils = require('web.utils'); +var session = require('web.session'); var CrashManager = require('web.CrashManager'); var core = require('web.core'); var _t = core._t; @@ -582,7 +584,7 @@ var StatementModel = BasicModel.extend({ })); } ids.push(line.id); - values.push({ + var values_dict = { "partner_id": line.st_line.partner_id, "counterpart_aml_dicts": _.map(_.filter(props, function (prop) { return !isNaN(prop.id) && !prop.already_paid; @@ -593,7 +595,23 @@ var StatementModel = BasicModel.extend({ "new_aml_dicts": _.map(_.filter(props, function (prop) { return isNaN(prop.id); }), self._formatToProcessReconciliation.bind(self, line)), - }); + }; + // If the lines are not fully balanced, create an unreconciled amount. + // line.st_line.currency_id is never false here because its equivalent to + // statement_line.currency_id or statement_line.journal_id.currency_id or statement_line.journal_id.company_id.currency_id (Python-side). + // see: get_statement_line_for_reconciliation_widget method in account/models/account_bank_statement.py for more details + var currency = session.get_currency(line.st_line.currency_id); + var balance = line.balance.amount; + if (!utils.float_is_zero(balance, currency.digits[1])) { + var unreconciled_amount_dict = { + 'account_id': line.st_line.open_balance_account_id, + 'credit': balance > 0 ? balance : 0, + 'debit': balance < 0 ? -balance : 0, + 'name': line.st_line.name + ' : ' + _t("Open balance"), + }; + values_dict['new_aml_dicts'].push(unreconciled_amount_dict); + } + values.push(values_dict); line.reconciled = true; self.valuenow++; }); diff --git a/addons/account/static/tests/reconciliation_tests.js b/addons/account/static/tests/reconciliation_tests.js index 30aec5679c8..a8fa2c6f233 100644 --- a/addons/account/static/tests/reconciliation_tests.js +++ b/addons/account/static/tests/reconciliation_tests.js @@ -548,7 +548,16 @@ QUnit.module('account', { var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options); testUtils.addMockEnvironment(clientAction, { - 'data': this.params.data, + data: this.params.data, + session: { + currencies: { + 3: { + digits: [69, 2], + position: "before", + symbol: "$" + } + } + }, }); clientAction.appendTo($('#qunit-fixture')); @@ -574,7 +583,16 @@ QUnit.module('account', { var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options); testUtils.addMockEnvironment(clientAction, { - 'data': this.params.data + data: this.params.data, + session: { + currencies: { + 3: { + digits: [69, 2], + position: "before", + symbol: "$" + } + } + }, }); clientAction.appendTo($('#qunit-fixture')); @@ -626,7 +644,7 @@ QUnit.module('account', { "credit": 525, "debit": 0, "name": "INV/2017/0003" - }], + }], payment_aml_ids: [], new_aml_dicts: []}]], "Should call process_reconciliations with ids"); }); @@ -651,12 +669,27 @@ QUnit.module('account', { partner_id: false, counterpart_aml_dicts:[], payment_aml_ids: [392], - new_aml_dicts: [] + new_aml_dicts: [ + { + "credit": 343.42, + "debit": 0, + "name": "Bank fees : Open balance" + } + ], }] ], "should call process_reconciliations with partial reconcile values"); } return this._super(route, args); }, + session: { + currencies: { + 3: { + digits: [69, 2], + position: "before", + symbol: "$" + } + } + }, }); clientAction.appendTo($('body')); @@ -813,11 +846,20 @@ QUnit.module('account', { var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options); testUtils.addMockEnvironment(clientAction, { - 'data': this.params.data, + data: this.params.data, + session: { + currencies: { + 3: { + digits: [69, 2], + position: "before", + symbol: "$" + } + } + }, }); clientAction.appendTo($('#qunit-fixture')); - assert.strictEqual(clientAction.$('.accounting_view tfoot .cell_right, .accounting_view tfoot .cell_left').text().replace(/[$, ]+/g, ''), "1175.0032.582000.00", "should display the open balance values"); + assert.strictEqual(clientAction.$('.accounting_view tfoot .cell_right, .accounting_view tfoot .cell_left').text().replace(/[$, ]+/g, ''), " 1175.00 32.58 2000.00", "should display the open balance values"); var widget = clientAction.widgets[0]; @@ -849,8 +891,8 @@ QUnit.module('account', { widget.$('.create .create_amount input').val('1100.00').trigger('input'); - assert.strictEqual(widget.$('.accounting_view tbody .cell_right').text(), "1100.00", "should display the value 1100.00 in right column"); - assert.strictEqual(widget.$('.accounting_view tfoot .cell_right').text(), "75.00", "should display 'Open Balance' line because the rest to reconcile is 75.00"); + assert.strictEqual(widget.$('.accounting_view tbody .cell_right').text(), "$ 1100.00", "should display the value 1100.00 in right column"); + assert.strictEqual(widget.$('.accounting_view tfoot .cell_right').text(), "$ 75.00", "should display 'Open Balance' line because the rest to reconcile is 75.00"); assert.strictEqual(widget.$('.accounting_view tbody tr').length, 1, "should have ever only the created reconcile line"); assert.strictEqual(widget.$('.accounting_view tbody tr').text().replace(/[\n\r\s$,]+/g, ' '), " 101200 SAJ/2014/002 and SAJ/2014/003 1100.00 ", "the new line should be update the amout"); @@ -862,9 +904,9 @@ QUnit.module('account', { $('.ui-autocomplete .ui-menu-item a:contains(101000 Current Assets)').trigger('mouseenter').trigger('click'); widget.$('.create .create_label input').val('test0').trigger('input'); - assert.strictEqual(widget.$('.accounting_view tbody .cell_left:last').text(), "100.00", "should display the value 100.00 in left column"); + assert.strictEqual(widget.$('.accounting_view tbody .cell_left:last').text(), "$ 100.00", "should display the value 100.00 in left column"); assert.strictEqual(widget.$('.accounting_view tfoot .cell_label').text(), "Open balance", "should display 'Open Balance'"); - assert.strictEqual(widget.$('.accounting_view tfoot .cell_right').text(), "175.00", "should display 'Open Balance' line because the rest to reconcile is 175.00"); + assert.strictEqual(widget.$('.accounting_view tfoot .cell_right').text(), "$ 175.00", "should display 'Open Balance' line because the rest to reconcile is 175.00"); assert.strictEqual(widget.$('.accounting_view tbody tr').length, 2, "should have 2 created reconcile lines"); assert.strictEqual(widget.$('.accounting_view tbody tr:eq(1)').text().replace(/[\n\r\s$,]+/g, ' '), " 101000 test0 100.00 ", "the new line should have the selected account, name and amout"); @@ -879,9 +921,9 @@ QUnit.module('account', { $('.ui-autocomplete .ui-menu-item a:contains(101000 Current Assets)').trigger('mouseenter').trigger('click'); widget.$('.create .create_label input').val('test1').trigger('input'); - assert.strictEqual(widget.$('.accounting_view tbody .cell_right:last').text(), "200.00", "should display the value 100.00 in left column"); + assert.strictEqual(widget.$('.accounting_view tbody .cell_right:last').text(), "$ 200.00", "should display the value 200.00 in left column"); assert.strictEqual(widget.$('.accounting_view tfoot .cell_label').text(), "Create Write-off", "should display 'Create Write-off'"); - assert.strictEqual(widget.$('.accounting_view tfoot .cell_left').text(), "25.00", "should display 'Create Write-off' with 25.00 in left column"); + assert.strictEqual(widget.$('.accounting_view tfoot .cell_left').text(), "$ 25.00", "should display 'Create Write-off' with 25.00 in left column"); assert.strictEqual(widget.$('.accounting_view tbody tr').length, 3, "should have 3 created reconcile lines"); clientAction.destroy(); @@ -1243,7 +1285,16 @@ QUnit.module('account', { var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options); testUtils.addMockEnvironment(clientAction, { - 'data': this.params.data, + data: this.params.data, + session: { + currencies: { + 3: { + digits: [69, 2], + position: "before", + symbol: "$" + } + } + }, }); clientAction.appendTo($('#qunit-fixture')); diff --git a/addons/account/views/account_view.xml b/addons/account/views/account_view.xml index 179043f60dd..325b7ef2ac5 100644 --- a/addons/account/views/account_view.xml +++ b/addons/account/views/account_view.xml @@ -2189,29 +2189,6 @@ - - account.report.hierarchy - account.financial.report - children_ids - - - - - - - - - - - - Financial Reports Hierarchy - account.financial.report - tree - - [('parent_id','=',False)] - - - diff --git a/addons/hr_expense/models/hr_expense.py b/addons/hr_expense/models/hr_expense.py index a2044c4bd25..cfbd6e77967 100644 --- a/addons/hr_expense/models/hr_expense.py +++ b/addons/hr_expense/models/hr_expense.py @@ -292,7 +292,7 @@ class HrExpense(models.Model): account_move.append(move_line) # Calculate tax lines and adjust base line - taxes = expense.tax_ids.compute_all(expense.unit_amount, expense.currency_id, expense.quantity, expense.product_id) + taxes = expense.tax_ids.with_context(round=True).compute_all(expense.unit_amount, expense.currency_id, expense.quantity, expense.product_id) account_move[-1]['price'] = taxes['total_excluded'] account_move[-1]['tax_ids'] = [(6, 0, expense.tax_ids.ids)] for tax in taxes['taxes']: diff --git a/addons/hr_org_chart/controllers/hr_org_chart.py b/addons/hr_org_chart/controllers/hr_org_chart.py index a088bb86663..5729997636f 100644 --- a/addons/hr_org_chart/controllers/hr_org_chart.py +++ b/addons/hr_org_chart/controllers/hr_org_chart.py @@ -10,12 +10,13 @@ class HrOrgChartController(http.Controller): _managers_level = 2 # FP request def _prepare_employee_data(self, employee): + job = employee.sudo().job_id return dict( id=employee.id, name=employee.name, link='/mail/view?model=hr.employee&res_id=%s' % employee.id, - job_id=employee.job_id.id, - job_name=employee.job_id.name or '', + job_id=job.id, + job_name=job.name or '', direct_sub_count=len(employee.child_ids), indirect_sub_count=employee.child_all_count, ) diff --git a/addons/pos_cache/models/pos_cache.py b/addons/pos_cache/models/pos_cache.py index bc6523c3eb5..10f617fe729 100644 --- a/addons/pos_cache/models/pos_cache.py +++ b/addons/pos_cache/models/pos_cache.py @@ -23,10 +23,10 @@ class pos_cache(models.Model): @api.one def refresh_cache(self): - products = self.env['product.product'].search(self.get_product_domain()) + Product = self.env['product.product'].sudo(self.compute_user_id.id) + products = Product.search(self.get_product_domain()) prod_ctx = products.with_context(pricelist=self.config_id.pricelist_id.id, display_default_code=False, lang=self.compute_user_id.lang) - prod_ctx = prod_ctx.sudo(self.compute_user_id.id) res = prod_ctx.read(self.get_product_fields()) datas = { 'cache': base64.encodestring(json.dumps(res)), diff --git a/addons/sale/models/account_invoice.py b/addons/sale/models/account_invoice.py index 1cf260da62e..9878e75ec04 100644 --- a/addons/sale/models/account_invoice.py +++ b/addons/sale/models/account_invoice.py @@ -111,7 +111,6 @@ class AccountInvoiceLine(models.Model): 'invoice_line_id', 'order_line_id', string='Sales Order Lines', readonly=True, copy=False) layout_category_id = fields.Many2one('sale.layout_category', string='Section') - layout_category_sequence = fields.Integer( - related='layout_category_id.sequence', - string='Layout Sequence', store=True) + layout_category_sequence = fields.Integer(string='Layout Sequence') + # TODO: remove layout_category_sequence in master or make it work properly price_total = fields.Monetary(compute='_compute_total_price', string='Total Amount', store=True) diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py index 4c8bee5cd81..43fd9942388 100644 --- a/addons/sale/models/sale.py +++ b/addons/sale/models/sale.py @@ -890,8 +890,8 @@ class SaleOrderLine(models.Model): amt_invoiced = fields.Monetary(string='Amount Invoiced', compute='_compute_invoice_amount', store=True) layout_category_id = fields.Many2one('sale.layout_category', string='Section') - layout_category_sequence = fields.Integer(related='layout_category_id.sequence', string='Layout Sequence', store=True) - # Store is intentionally set in order to keep the "historic" order. + layout_category_sequence = fields.Integer(string='Layout Sequence') + # TODO: remove layout_category_sequence in master or make it work properly @api.multi def _prepare_invoice_line(self, qty): diff --git a/addons/stock/models/stock_quant.py b/addons/stock/models/stock_quant.py index ca60cb50239..c6d5700c366 100644 --- a/addons/stock/models/stock_quant.py +++ b/addons/stock/models/stock_quant.py @@ -407,7 +407,8 @@ class QuantPackage(models.Model): def action_view_picking(self): action = self.env.ref('stock.action_picking_tree_all').read()[0] - pickings = self.env['stock.move.line'].search([('result_package_id', 'in', self.ids)]).mapped('picking_id') + domain = ['|', ('result_package_id', 'in', self.ids), ('package_id', 'in', self.ids)] + pickings = self.env['stock.move.line'].search(domain).mapped('picking_id') action['domain'] = [('id', 'in', pickings.ids)] return action diff --git a/addons/web/static/src/js/fields/field_utils.js b/addons/web/static/src/js/fields/field_utils.js index 369dda9f037..7ba4122cccb 100644 --- a/addons/web/static/src/js/fields/field_utils.js +++ b/addons/web/static/src/js/fields/field_utils.js @@ -418,13 +418,36 @@ function parseDateTime(value, field, options) { throw new Error(_.str.sprintf(core._t("'%s' is not a correct datetime"), value)); } -function parseFloat(value) { +/** + * Parse a String containing number in language formating + * + * @param {string} value + * The string to be parsed with the setting of thousands and + * decimal separator + * @returns {float|NaN} the number value contained in the string representation + */ +function parseNumber(value) { if (core._t.database.parameters.thousands_sep) { var escapedSep = _.str.escapeRegExp(core._t.database.parameters.thousands_sep); value = value.replace(new RegExp(escapedSep, 'g'), ''); } - value = value.replace(core._t.database.parameters.decimal_point, '.'); - var parsed = Number(value); + if (core._t.database.parameters.decimal_point) { + value = value.replace(core._t.database.parameters.decimal_point, '.'); + } + return Number(value); +} + +/** + * Parse a String containing float in language formating + * + * @param {string} value + * The string to be parsed with the setting of thousands and + * decimal separator + * @returns {float} + * @throws {Error} if no float is found respecting the language configuration + */ +function parseFloat(value) { + var parsed = parseNumber(value); if (isNaN(parsed)) { throw new Error(_.str.sprintf(core._t("'%s' is not a correct float"), value)); } @@ -491,9 +514,17 @@ function parseFloatTime(value) { return factor * (hours + (minutes / 60)); } +/** + * Parse a String containing integer with language formating + * + * @param {string} value + * The string to be parsed with the setting of thousands and + * decimal separator + * @returns {integer} + * @throws {Error} if no integer is found respecting the language configuration + */ function parseInteger(value) { - value = value.replace(new RegExp(core._t.database.parameters.thousands_sep, "g"), ''); - var parsed = Number(value); + var parsed = parseNumber(value); // do not accept not numbers or float values if (isNaN(parsed) || parsed % 1 || parsed < -2147483648 || parsed > 2147483647) { throw new Error(_.str.sprintf(core._t("'%s' is not a correct integer"), value)); diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js index ed5f03b4f9b..c4422285ae7 100644 --- a/addons/web/static/src/js/fields/relational_fields.js +++ b/addons/web/static/src/js/fields/relational_fields.js @@ -423,7 +423,7 @@ var FieldMany2One = AbstractField.extend({ _updateExternalButton: function () { var has_external_button = !this.nodeOptions.no_open && !this.floating && this.isSet(); this.$external_button.toggle(has_external_button); - this.$el.toggleClass('o_with_button', has_external_button); + this.$el.toggleClass('o_with_button', has_external_button); // Should not be required anymore but kept for compatibility }, @@ -1943,7 +1943,7 @@ var FieldSelection = AbstractField.extend({ for (var i = 0 ; i < this.values.length ; i++) { this.$el.append($('