diff --git a/addons/account/models/account.py b/addons/account/models/account.py index 45273bfbca8..e2f8e6eaeaa 100644 --- a/addons/account/models/account.py +++ b/addons/account/models/account.py @@ -745,7 +745,12 @@ class AccountTax(models.Model): if not round_tax: prec += 5 - total_excluded = total_included = base = round(price_unit * quantity, prec) + + base_values = self.env.context.get('base_values') + if not base_values: + total_excluded = total_included = base = round(price_unit * quantity, prec) + else: + total_excluded, total_included, base = base_values # Sorting key is mandatory in this case. When no key is provided, sorted() will perform a # search. However, the search method is overridden in account.tax in order to add a domain @@ -753,9 +758,10 @@ class AccountTax(models.Model): # case of group taxes. for tax in self.sorted(key=lambda r: r.sequence): if tax.amount_type == 'group': - ret = tax.children_tax_ids.compute_all(price_unit, currency, quantity, product, partner) + children = tax.children_tax_ids.with_context(base_values=(total_excluded, total_included, base)) + ret = children.compute_all(price_unit, currency, quantity, product, partner) total_excluded = ret['total_excluded'] - base = ret['base'] + base = ret['base'] if tax.include_base_amount else base total_included = ret['total_included'] tax_amount = total_included - total_excluded taxes += ret['taxes'] diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py index 204f0161a85..931ae7f41df 100644 --- a/addons/account/models/account_bank_statement.py +++ b/addons/account/models/account_bank_statement.py @@ -472,7 +472,7 @@ class AccountBankStatementLine(models.Model): 'details': { 'name': _("Automatically reconciled items"), 'model': 'account.move', - 'ids': automatic_reconciliation_entries.ids + 'ids': automatic_reconciliation_entries.mapped('journal_entry_ids').ids } }] return { diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index 4e8e5c9c0e4..efbbeb17011 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -612,6 +612,14 @@ class AccountInvoice(models.Model): 'account_analytic_id': tax['analytic'] and line.account_analytic_id.id or False, 'account_id': self.type in ('out_invoice', 'in_invoice') and (tax['account_id'] or line.account_id.id) or (tax['refund_account_id'] or line.account_id.id), } + + # If the taxes generate moves on the same financial account as the invoice line, + # propagate the analytic account from the invoice line to the tax line. + # This is necessary in situations were (part of) the taxes cannot be reclaimed, + # to ensure the tax move is allocated to the proper analytic account. + if not vals.get('account_analytic_id') and line.account_analytic_id and vals['account_id'] == line.account_id.id: + vals['account_analytic_id'] = line.account_analytic_id.id + return vals @api.multi @@ -622,14 +630,6 @@ class AccountInvoice(models.Model): taxes = line.invoice_line_tax_ids.compute_all(price_unit, self.currency_id, line.quantity, line.product_id, self.partner_id)['taxes'] for tax in taxes: val = self._prepare_tax_line_vals(line, tax) - - # If the taxes generate moves on the same financial account as the invoice line, - # propagate the analytic account from the invoice line to the tax line. - # This is necessary in situations were (part of) the taxes cannot be reclaimed, - # to ensure the tax move is allocated to the proper analytic account. - if not val.get('account_analytic_id') and line.account_analytic_id and val['account_id'] == line.account_id.id: - val['account_analytic_id'] = line.account_analytic_id.id - key = self.env['account.tax'].browse(tax['id']).get_grouping_key(val) if key not in tax_grouped: @@ -1350,15 +1350,17 @@ class AccountInvoiceTax(models.Model): for invoice in self.mapped('invoice_id'): tax_grouped[invoice.id] = invoice.get_taxes_values() for tax in self: - key = self.env['account.tax'].browse(tax.tax_id.id).get_grouping_key({ - 'tax_id': tax.tax_id.id, - 'account_id': tax.account_id.id, - 'account_analytic_id': tax.account_analytic_id.id, - }) - if tax.invoice_id and key in tax_grouped[tax.invoice_id.id]: - tax.base = tax_grouped[tax.invoice_id.id][key]['base'] - else: - _logger.warning('Tax Base Amount not computable probably due to a change in an underlying tax (%s).', tax.tax_id.name) + tax.base = 0.0 + if tax.tax_id: + key = tax.tax_id.get_grouping_key({ + 'tax_id': tax.tax_id.id, + 'account_id': tax.account_id.id, + 'account_analytic_id': tax.account_analytic_id.id, + }) + if tax.invoice_id and key in tax_grouped[tax.invoice_id.id]: + tax.base = tax_grouped[tax.invoice_id.id][key]['base'] + else: + _logger.warning('Tax Base Amount not computable probably due to a change in an underlying tax (%s).', tax.tax_id.name) invoice_id = fields.Many2one('account.invoice', string='Invoice', ondelete='cascade', index=True) name = fields.Char(string='Tax Description', required=True) diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index 64a5c538841..01d3b8199f9 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -981,6 +981,9 @@ class AccountMoveLine(models.Model): return True rec_move_ids = self.env['account.partial.reconcile'] for account_move_line in self: + for invoice in account_move_line.payment_id.invoice_ids: + if account_move_line in invoice.payment_move_line_ids: + account_move_line.payment_id.write({'invoice_ids': [(3, invoice.id, None)]}) rec_move_ids += account_move_line.matched_debit_ids rec_move_ids += account_move_line.matched_credit_ids return rec_move_ids.unlink() diff --git a/addons/account/report/account_invoice_report.py b/addons/account/report/account_invoice_report.py index 9d0b3d3da27..eb969cadec4 100644 --- a/addons/account/report/account_invoice_report.py +++ b/addons/account/report/account_invoice_report.py @@ -45,7 +45,7 @@ class AccountInvoiceReport(models.Model): user_currency_price_total = fields.Float(string="Total Without Tax", compute='_compute_amounts_in_user_currency', digits=0) price_average = fields.Float(string='Average Price', readonly=True, group_operator="avg") user_currency_price_average = fields.Float(string="Average Price", compute='_compute_amounts_in_user_currency', digits=0) - currency_rate = fields.Float(string='Currency Rate', readonly=True) + currency_rate = fields.Float(string='Currency Rate', readonly=True, group_operator="avg") nbr = fields.Integer(string='# of Lines', readonly=True) # TDE FIXME master: rename into nbr_lines type = fields.Selection([ ('out_invoice', 'Customer Invoice'), diff --git a/addons/account/tests/test_tax.py b/addons/account/tests/test_tax.py index d626f4e20e0..84aa784fad3 100644 --- a/addons/account/tests/test_tax.py +++ b/addons/account/tests/test_tax.py @@ -43,10 +43,46 @@ class TestTax(AccountTestUsers): (4, self.percent_tax.id, 0) ] }) + self.group_tax_bis = self.tax_model.create({ + 'name': "Group tax bis", + 'amount_type': 'group', + 'amount': 0, + 'sequence': 6, + 'children_tax_ids': [ + (4, self.fixed_tax.id, 0), + (4, self.percent_tax.id, 0) + ] + }) + self.group_of_group_tax = self.tax_model.create({ + 'name': "Group of group tax", + 'amount_type': 'group', + 'amount': 0, + 'sequence': 7, + 'children_tax_ids': [ + (4, self.group_tax.id, 0), + (4, self.group_tax_bis.id, 0) + ] + }) self.bank_journal = self.env['account.journal'].search([('type', '=', 'bank'), ('company_id', '=', self.account_manager.company_id.id)])[0] self.bank_account = self.bank_journal.default_debit_account_id self.expense_account = self.env['account.account'].search([('user_type_id.type', '=', 'payable')], limit=1) #Should be done by onchange later + def test_tax_group_of_group_tax(self): + self.fixed_tax.include_base_amount = True + self.group_tax.include_base_amount = True + self.group_of_group_tax.include_base_amount = True + res = self.group_of_group_tax.compute_all(200.0) + self.assertEquals(res['total_excluded'], 200.0) + # After calculation of first group + # base = 210 + # total_included = 231 + # Base of the first grouped is passed + # Base after the second group (220) is dropped. + # Base of the group of groups is passed out, + # so we obtain base as after first group + self.assertEquals(res['base'], 210.0) + self.assertEquals(res['total_included'], 263.0) + def test_tax_group(self): res = self.group_tax.compute_all(200.0) self.assertEquals(res['total_excluded'], 200.0) diff --git a/addons/account/views/account_invoice_view.xml b/addons/account/views/account_invoice_view.xml index 568bdead85d..e1da859f9d0 100644 --- a/addons/account/views/account_invoice_view.xml +++ b/addons/account/views/account_invoice_view.xml @@ -135,8 +135,8 @@ - - + + @@ -200,8 +200,8 @@ - - + + diff --git a/addons/account/views/account_payment_view.xml b/addons/account/views/account_payment_view.xml index 1f12802b74c..a14c4c73ffa 100644 --- a/addons/account/views/account_payment_view.xml +++ b/addons/account/views/account_payment_view.xml @@ -12,7 +12,7 @@ - + @@ -31,7 +31,7 @@ - + @@ -149,7 +149,7 @@

- + Payments account.payment diff --git a/addons/account/views/web_planner_data.xml b/addons/account/views/web_planner_data.xml index de159a85cf0..b3569deb76b 100644 --- a/addons/account/views/web_planner_data.xml +++ b/addons/account/views/web_planner_data.xml @@ -409,7 +409,7 @@

Revenue Recognition

Use in conjunction with contracts to calculate your monthly revenue for multi-month contracts.

- Install Now diff --git a/addons/board/static/src/js/dashboard.js b/addons/board/static/src/js/dashboard.js index 87ff74f826a..3575f36f655 100644 --- a/addons/board/static/src/js/dashboard.js +++ b/addons/board/static/src/js/dashboard.js @@ -139,7 +139,11 @@ var DashBoard = form_common.FormWidget.extend({ }, on_close_action: function(e) { if (confirm(_t("Are you sure you want to remove this item ?"))) { - $(e.currentTarget).parents('.oe_action:first').remove(); + var $container = $(e.currentTarget).parents('.oe_action:first'); + var am = _.findWhere(this.action_managers, { am_id: $container.data('am_id') }); + am.destroy(); + this.action_managers.splice(_.indexOf(this.action_managers, am), 1); + $container.remove(); this.do_save_dashboard(); } }, @@ -223,7 +227,13 @@ var DashBoard = form_common.FormWidget.extend({ var am = new ActionManager(this), // FIXME: ideally the dashboard view shall be refactored like kanban. $action = $('#' + this.view.element_id + '_action_' + index); - $action.parent().data('action_attrs', action_attrs); + var $action_container = $action.closest('.oe_action'); + var am_id = _.uniqueId('action_manager_'); + am.am_id = am_id; + $action_container.data({ + action_attrs: action_attrs, + am_id: am_id, + }); this.action_managers.push(am); am.appendTo($action).then(function () { am.do_action(action).then(function () { diff --git a/addons/hr_expense/models/hr_expense.py b/addons/hr_expense/models/hr_expense.py index fa4c6498366..2756b0ebd42 100644 --- a/addons/hr_expense/models/hr_expense.py +++ b/addons/hr_expense/models/hr_expense.py @@ -171,79 +171,71 @@ class HrExpense(models.Model): ''' main function that is called when trying to create the accounting entries related to an expense ''' - journal_dict = {} for expense in self: - acc_date = expense.sheet_id.accounting_date or fields.Date.context_today(self) - jrn = expense.sheet_id.bank_journal_id if expense.payment_mode == 'company_account' else expense.sheet_id.journal_id - journal_dict.setdefault(jrn, self.env['hr.expense']) - journal_dict[jrn] |= expense - - for journal, expense_list in journal_dict.items(): + journal = expense.sheet_id.bank_journal_id if expense.payment_mode == 'company_account' else expense.sheet_id.journal_id #create the move that will contain the accounting entries - # reference if the same for every expense - ref = len(expense_list.mapped('employee_id.address_home_id.ref')) == 1 and expense_list[0].employee_id.address_home_id.ref or False + acc_date = expense.sheet_id.accounting_date or expense.date move = self.env['account.move'].create({ 'journal_id': journal.id, 'company_id': self.env.user.company_id.id, 'date': acc_date, - 'ref': ref, + 'ref': expense.sheet_id.name, # force the name to the default value, to avoid an eventual 'default_name' in the context # to set it to '' which cause no number to be given to the account.move when posted. 'name': '/', }) - for expense in expense_list: - company_currency = expense.company_id.currency_id - diff_currency_p = expense.currency_id != company_currency - #one account.move.line per expense (+taxes..) - move_lines = expense._move_line_get() + company_currency = expense.company_id.currency_id + diff_currency_p = expense.currency_id != company_currency + #one account.move.line per expense (+taxes..) + move_lines = expense._move_line_get() - #create one more move line, a counterline for the total on payable account - payment_id = False - total, total_currency, move_lines = expense._compute_expense_totals(company_currency, move_lines, acc_date) - if expense.payment_mode == 'company_account': - if not expense.sheet_id.bank_journal_id.default_credit_account_id: - raise UserError(_("No credit account found for the %s journal, please configure one.") % (expense.sheet_id.bank_journal_id.name)) - emp_account = expense.sheet_id.bank_journal_id.default_credit_account_id.id - journal = expense.bank_journal_id - #create payment - payment_methods = (total < 0) and journal.outbound_payment_method_ids or journal.inbound_payment_method_ids - journal_currency = journal.currency_id or journal.company_id.currency_id - payment = self.env['account.payment'].create({ - 'payment_method_id': payment_methods and payment_methods[0].id or False, - 'payment_type': total < 0 and 'outbound' or 'inbound', - 'partner_id': expense.employee_id.address_home_id.commercial_partner_id.id, - 'partner_type': 'supplier', - 'journal_id': journal.id, - 'payment_date': expense.date, - 'state': 'reconciled', - 'currency_id': diff_currency_p and expense.currency_id.id or journal_currency.id, - 'amount': diff_currency_p and abs(total_currency) or abs(total), - 'name': expense.name, + #create one more move line, a counterline for the total on payable account + payment_id = False + total, total_currency, move_lines = expense._compute_expense_totals(company_currency, move_lines, acc_date) + if expense.payment_mode == 'company_account': + if not expense.sheet_id.bank_journal_id.default_credit_account_id: + raise UserError(_("No credit account found for the %s journal, please configure one.") % (expense.sheet_id.bank_journal_id.name)) + emp_account = expense.sheet_id.bank_journal_id.default_credit_account_id.id + journal = expense.sheet_id.bank_journal_id + #create payment + payment_methods = (total < 0) and journal.outbound_payment_method_ids or journal.inbound_payment_method_ids + journal_currency = journal.currency_id or journal.company_id.currency_id + payment = self.env['account.payment'].create({ + 'payment_method_id': payment_methods and payment_methods[0].id or False, + 'payment_type': total < 0 and 'outbound' or 'inbound', + 'partner_id': expense.employee_id.address_home_id.commercial_partner_id.id, + 'partner_type': 'supplier', + 'journal_id': journal.id, + 'payment_date': expense.date, + 'state': 'reconciled', + 'currency_id': diff_currency_p and expense.currency_id.id or journal_currency.id, + 'amount': diff_currency_p and abs(total_currency) or abs(total), + 'name': expense.name, + }) + payment_id = payment.id + else: + if not expense.employee_id.address_home_id: + raise UserError(_("No Home Address found for the employee %s, please configure one.") % (expense.employee_id.name)) + emp_account = expense.employee_id.address_home_id.property_account_payable_id.id + + move_lines.append({ + 'type': 'dest', + 'name': expense.employee_id.name, + 'price': total, + 'account_id': emp_account, + 'date_maturity': acc_date, + 'amount_currency': diff_currency_p and total_currency or False, + 'currency_id': diff_currency_p and expense.currency_id.id or False, + 'payment_id': payment_id, }) - payment_id = payment.id - else: - if not expense.employee_id.address_home_id: - raise UserError(_("No Home Address found for the employee %s, please configure one.") % (expense.employee_id.name)) - emp_account = expense.employee_id.address_home_id.property_account_payable_id.id - move_lines.append({ - 'type': 'dest', - 'name': expense.employee_id.name, - 'price': total, - 'account_id': emp_account, - 'date_maturity': acc_date, - 'amount_currency': diff_currency_p and total_currency or False, - 'currency_id': diff_currency_p and expense.currency_id.id or False, - 'payment_id': payment_id, - }) - - #convert eml into an osv-valid format - lines = map(lambda x: (0, 0, expense._prepare_move_line(x)), move_lines) - move.write({'line_ids': lines}) - expense.sheet_id.write({'account_move_id': move.id}) - if expense.payment_mode == 'company_account': - expense.sheet_id.paid_expense_sheets() + #convert eml into an osv-valid format + lines = map(lambda x: (0, 0, expense._prepare_move_line(x)), move_lines) + move.write({'line_ids': lines}) + expense.sheet_id.write({'account_move_id': move.id}) move.post() + if expense.payment_mode == 'company_account': + expense.sheet_id.paid_expense_sheets() return True @api.multi @@ -526,3 +518,11 @@ class HrExpenseSheet(models.Model): res['domain'] = [('res_model', '=', 'hr.expense'), ('res_id', 'in', self.expense_line_ids.ids)] res['context'] = {'default_res_model': 'hr.expense.sheet', 'default_res_id': self.id} return res + + @api.multi + def action_open_journal_entries(self): + res = self.env['ir.actions.act_window'].for_xml_id('account', 'action_move_journal_line') + refs = [x.name for x in self] + res['domain'] = [('ref', 'in', refs)] + res['context'] = {} + return res diff --git a/addons/hr_expense/views/hr_expense_views.xml b/addons/hr_expense/views/hr_expense_views.xml index 255778731b8..5088ff3b571 100644 --- a/addons/hr_expense/views/hr_expense_views.xml +++ b/addons/hr_expense/views/hr_expense_views.xml @@ -422,6 +422,13 @@ type="object"> +