diff --git a/addons/account/models/account.py b/addons/account/models/account.py index a06a5bfd634..7f4b044d5f3 100644 --- a/addons/account/models/account.py +++ b/addons/account/models/account.py @@ -736,6 +736,9 @@ class AccountTax(models.Model): else: total_included += tax_amount + # Keep base amount used for the current tax + tax_base = base + if tax.include_base_amount: base += tax_amount @@ -743,6 +746,7 @@ class AccountTax(models.Model): 'id': tax.id, 'name': tax.with_context(**{'lang': partner.lang} if partner else {}).name, 'amount': tax_amount, + 'base': tax_base, 'sequence': tax.sequence, 'account_id': tax.account_id.id, 'refund_account_id': tax.refund_account_id.id, diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py index 32cbefa051a..03e2ea03f57 100644 --- a/addons/account/models/account_bank_statement.py +++ b/addons/account/models/account_bank_statement.py @@ -374,6 +374,7 @@ class AccountBankStatementLine(models.Model): journal_entry_ids = fields.One2many('account.move', 'statement_line_id', 'Journal Entries', copy=False, readonly=True) amount_currency = fields.Monetary(help="The amount expressed in an optional other currency if it is a multi-currency entry.") currency_id = fields.Many2one('res.currency', string='Currency', help="The optional other currency if it is a multi-currency entry.") + state = fields.Selection(related='statement_id.state' , string='Status', readonly=True) @api.one @api.constrains('amount') diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index 7c301821d45..89373e711df 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -12,6 +12,9 @@ from openerp.tools.misc import formatLang from openerp.exceptions import UserError, RedirectWarning, ValidationError import openerp.addons.decimal_precision as dp +import logging + +_logger = logging.getLogger(__name__) # mapping invoice type to journal type TYPE2JOURNAL = { @@ -544,6 +547,25 @@ class AccountInvoice(models.Model): else: return self.env.ref('account.invoice_form').id + def _prepare_tax_line_vals(self, line, tax): + """ Prepare values to create an account.invoice.tax line + + The line parameter is an account.invoice.line, and the + tax parameter is the output of account.tax.compute_all(). + """ + vals = { + 'invoice_id': self.id, + 'name': tax['name'], + 'tax_id': tax['id'], + 'amount': tax['amount'], + 'base': tax['base'], + 'manual': False, + 'sequence': tax['sequence'], + '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), + } + return vals + @api.multi def get_taxes_values(self): tax_grouped = {} @@ -551,16 +573,7 @@ class AccountInvoice(models.Model): price_unit = line.price_unit * (1 - (line.discount or 0.0) / 100.0) 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 = { - 'invoice_id': self.id, - 'name': tax['name'], - 'tax_id': tax['id'], - 'amount': tax['amount'], - 'manual': False, - 'sequence': tax['sequence'], - '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), - } + 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. @@ -575,6 +588,7 @@ class AccountInvoice(models.Model): tax_grouped[key] = val else: tax_grouped[key]['amount'] += val['amount'] + tax_grouped[key]['base'] += val['base'] return tax_grouped @api.multi @@ -1306,15 +1320,19 @@ class AccountInvoiceTax(models.Model): _order = 'sequence' def _compute_base_amount(self): + tax_grouped = {} + for invoice in self.mapped('invoice_id'): + tax_grouped[invoice.id] = invoice.get_taxes_values() for tax in self: - base = 0.0 - for line in tax.invoice_id.invoice_line_ids: - if tax.tax_id in line.invoice_line_tax_ids: - price_unit = line.price_unit * (1 - (line.discount or 0.0) / 100.0) - base += (line.invoice_line_tax_ids - tax.tax_id).compute_all( - price_unit, line.invoice_id.currency_id, line.quantity, line.product_id, line.invoice_id.partner_id - )['base'] - tax.base = base + 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) 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/tests/test_account_supplier_invoice.py b/addons/account/tests/test_account_supplier_invoice.py index afe8ddc867a..27dc4b13ebd 100644 --- a/addons/account/tests/test_account_supplier_invoice.py +++ b/addons/account/tests/test_account_supplier_invoice.py @@ -41,3 +41,65 @@ 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_supplier_invoice2(self): + tax_fixed = self.env['account.tax'].create({ + 'sequence': 10, + 'name': 'Tax 10.0 (Fixed)', + 'amount': 10.0, + 'amount_type': 'fixed', + 'include_base_amount': True, + }) + tax_percent_included_base_incl = self.env['account.tax'].create({ + 'sequence': 20, + 'name': 'Tax 50.0% (Percentage of Price Tax Included)', + 'amount': 50.0, + 'amount_type': 'division', + 'include_base_amount': True, + }) + tax_percentage = self.env['account.tax'].create({ + 'sequence': 30, + 'name': 'Tax 20.0% (Percentage of Price)', + 'amount': 20.0, + 'amount_type': 'percent', + 'include_base_amount': False, + }) + analytic_account = self.env['account.analytic.account'].create({ + 'name': 'test account', + }) + + # Should be changed by automatic on_change later + invoice_account = self.env['account.account'].search([('user_type_id', '=', self.env.ref('account.data_account_type_receivable').id)], limit=1).id + invoice_line_account = self.env['account.account'].search([('user_type_id', '=', self.env.ref('account.data_account_type_expenses').id)], limit=1).id + + invoice = self.env['account.invoice'].create({'partner_id': self.env.ref('base.res_partner_2').id, + 'account_id': invoice_account, + 'type': 'in_invoice', + }) + + invoice_line = self.env['account.invoice.line'].create({'product_id': self.env.ref('product.product_product_4').id, + 'quantity': 5.0, + 'price_unit': 100.0, + 'invoice_id': invoice.id, + 'name': 'product that cost 100', + 'account_id': invoice_line_account, + 'invoice_line_tax_ids': [(6, 0, [tax_fixed.id, tax_percent_included_base_incl.id, tax_percentage.id])], + 'account_analytic_id': analytic_account.id, + }) + invoice.compute_taxes() + + # check that Initially supplier bill state is "Draft" + self.assertTrue((invoice.state == 'draft'), "Initially vendor bill state is Draft") + + #change the state of invoice to open by clicking Validate button + invoice.signal_workflow('invoice_open') + + # Check if amount and corresponded base is correct for all tax scenarios given on a computational base + # Keep in mind that tax amount can be changed by the user at any time before validating (based on the invoice and tax laws applicable) + invoice_tax = invoice.tax_line_ids.sorted(key=lambda r: r.sequence) + self.assertEquals(invoice_tax.mapped('amount'), [50.0, 550.0, 220.0]) + self.assertEquals(invoice_tax.mapped('base'), [500.0, 550.0, 1100.0]) + + #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() diff --git a/addons/account/views/web_planner_data.xml b/addons/account/views/web_planner_data.xml index 7f0af5daa35..b6e8d1c8570 100644 --- a/addons/account/views/web_planner_data.xml +++ b/addons/account/views/web_planner_data.xml @@ -446,7 +446,7 @@
Manage time & material contracts or fixed-price recurring subscriptions.
- Install Now diff --git a/addons/account_asset/account_asset.py b/addons/account_asset/account_asset.py index 745358e67b9..c706529cb4d 100644 --- a/addons/account_asset/account_asset.py +++ b/addons/account_asset/account_asset.py @@ -147,7 +147,7 @@ class AccountAssetAsset(models.Model): else: if self.method == 'linear': amount = amount_to_depr / (undone_dotation_number - len(posted_depreciation_line_ids)) - if self.prorata and self.category_id.type == 'purchase': + if self.prorata: amount = amount_to_depr / self.method_number if sequence == 1: days = (self.company_id.compute_fiscalyear_dates(depreciation_date)['date_to'] - depreciation_date).days + 1 @@ -168,7 +168,7 @@ class AccountAssetAsset(models.Model): while depreciation_date <= end_date: depreciation_date = date(depreciation_date.year, depreciation_date.month, depreciation_date.day) + relativedelta(months=+self.method_period) undone_dotation_number += 1 - if self.prorata and self.category_id.type == 'purchase': + if self.prorata: undone_dotation_number += 1 return undone_dotation_number diff --git a/addons/account_cancel/account_cancel_view.xml b/addons/account_cancel/account_cancel_view.xml index 017f5a24832..3fc598a8483 100644 --- a/addons/account_cancel/account_cancel_view.xml +++ b/addons/account_cancel/account_cancel_view.xml @@ -54,7 +54,8 @@