diff --git a/README.md b/README.md index 4432013ba16..f8bfd4cf38f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![Build Status](http://runbot.odoo.com/runbot/badge/flat/1/master.svg)](http://runbot.odoo.com/runbot) -[![Tech Doc](http://img.shields.io/badge/master-docs-8f8f8f.svg?style=flat)](http://www.odoo.com/documentation/master) -[![Help](http://img.shields.io/badge/master-help-8f8f8f.svg?style=flat)](https://www.odoo.com/forum/help-1) -[![Nightly Builds](http://img.shields.io/badge/master-nightly-8f8f8f.svg?style=flat)](http://nightly.odoo.com/) +[![Tech Doc](http://img.shields.io/badge/master-docs-875A7B.svg?style=flat)](http://www.odoo.com/documentation/master) +[![Help](http://img.shields.io/badge/master-help-875A7B.svg?style=flat)](https://www.odoo.com/forum/help-1) +[![Nightly Builds](http://img.shields.io/badge/master-nightly-875A7B.svg?style=flat)](http://nightly.odoo.com/) Odoo ---- @@ -27,14 +27,14 @@ a full-featured Open Source ERP when you inst Getting started with Odoo ------------------------- -For a standard installation please follow the Setup instructions +For a standard installation please follow the Setup instructions from the documentation. If you are a developer you may type the following command at your terminal: - wget -O- https://raw.githubusercontent.com/odoo/odoo/10.0/setup/setup_dev.py | python + wget -O- https://raw.githubusercontent.com/odoo/odoo/master/setup/setup_dev.py | python -Then follow the developer tutorials +Then follow the developer tutorials For Odoo employees diff --git a/addons/account/models/account.py b/addons/account/models/account.py index 0271cad8aa5..eec37901431 100644 --- a/addons/account/models/account.py +++ b/addons/account/models/account.py @@ -731,6 +731,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 @@ -738,6 +741,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 a23e1d12b5c..204f0161a85 100644 --- a/addons/account/models/account_bank_statement.py +++ b/addons/account/models/account_bank_statement.py @@ -250,13 +250,13 @@ class AccountBankStatement(models.Model): context = dict(self._context or {}) context['journal_id'] = self.journal_id.id return { - 'name': _('Journal Items'), + 'name': _('Journal Entries'), 'view_type': 'form', 'view_mode': 'tree,form', - 'res_model': 'account.move.line', + 'res_model': 'account.move', 'view_id': False, 'type': 'ir.actions.act_window', - 'domain': [('statement_id', 'in', self.ids)], + 'domain': [('id', 'in', self.mapped('move_line_ids').mapped('move_id').ids)], 'context': context, } @@ -366,6 +366,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) move_name = fields.Char(string='Journal Entry Name', readonly=True, default=False, copy=False, help="Technical field holding the number given to the journal entry, automatically set when the statement line is reconciled then stored to set the same number again if the line is cancelled, set to draft and re-processed again.") diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index 04488a6ea8e..d7bec0cb579 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -12,6 +12,9 @@ from odoo.tools.misc import formatLang from odoo.exceptions import UserError, RedirectWarning, ValidationError import odoo.addons.decimal_precision as dp +import logging + +_logger = logging.getLogger(__name__) # mapping invoice type to journal type TYPE2JOURNAL = { @@ -592,6 +595,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 = {} @@ -599,16 +621,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. @@ -623,6 +636,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 @@ -1336,15 +1350,19 @@ class AccountInvoiceTax(models.Model): @api.depends('invoice_id.invoice_line_ids') 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/models/account_move.py b/addons/account/models/account_move.py index b9d80ea21cc..5ff889f6025 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -1037,10 +1037,13 @@ class AccountMoveLine(models.Model): # the provided values were not already multi-currency if account.currency_id and 'amount_currency' not in vals and account.currency_id.id != account.company_id.currency_id.id: vals['currency_id'] = account.currency_id.id - ctx = {} - if 'date' in vals: - ctx['date'] = vals['date'] - vals['amount_currency'] = account.company_id.currency_id.with_context(ctx).compute(amount, account.currency_id) + if self._context.get('skip_full_reconcile_check') == 'amount_currency_excluded': + vals['amount_currency'] = 0.0 + else: + ctx = {} + if 'date' in vals: + ctx['date'] = vals['date'] + vals['amount_currency'] = account.company_id.currency_id.with_context(ctx).compute(amount, account.currency_id) if not ok: raise UserError(_('You cannot use this general account in this journal, check the tab \'Entry Controls\' on the related journal.')) diff --git a/addons/account/models/chart_template.py b/addons/account/models/chart_template.py index 0bf832c133d..a7841f307c4 100644 --- a/addons/account/models/chart_template.py +++ b/addons/account/models/chart_template.py @@ -39,7 +39,7 @@ def migrate_tags_on_taxes(cr, registry): ('description', '=', tax_template.description) ]) if len(tax_id.ids) == 1: - tax_id.sudo().write({'tag_ids': [(6, 0, [tax_template.tag_ids.ids])]}) + tax_id.sudo().write({'tag_ids': [(6, 0, tax_template.tag_ids.ids)]}) # --------------------------------------------------------------- # Account Templates: Account, Tax, Tax Code and chart. + Wizard @@ -399,7 +399,9 @@ class AccountChartTemplate(models.Model): :rtype: dict """ self.ensure_one() - account_reconcile_models = self.env['account.reconcile.model.template'].search([]) + account_reconcile_models = self.env['account.reconcile.model.template'].search([ + ('account_id.chart_template_id', '=', self.id) + ]) for account_reconcile_model in account_reconcile_models: vals = { 'name': account_reconcile_model.name, diff --git a/addons/account/models/partner.py b/addons/account/models/partner.py index 7272a77345e..3ca9f8f10ee 100644 --- a/addons/account/models/partner.py +++ b/addons/account/models/partner.py @@ -445,5 +445,5 @@ class ResPartner(models.Model): ''' action = self.env.ref('account.action_invoice_refund_out_tree') result = action.read()[0] - result['domain'] = "[('id','in',[" + ','.join(map(str, self.ids)) + "])]" + result['domain'] = [('partner_id', 'in', self.ids)] return result diff --git a/addons/account/tests/test_account_supplier_invoice.py b/addons/account/tests/test_account_supplier_invoice.py index 671cfb2dcb0..232bd39ddc7 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/account_view.xml b/addons/account/views/account_view.xml index 23021226ace..033394aa7e7 100644 --- a/addons/account/views/account_view.xml +++ b/addons/account/views/account_view.xml @@ -556,7 +556,7 @@
@@ -884,6 +884,7 @@ + diff --git a/addons/account/views/partner_view.xml b/addons/account/views/partner_view.xml index f4bb90a1236..025be73ab0e 100644 --- a/addons/account/views/partner_view.xml +++ b/addons/account/views/partner_view.xml @@ -102,7 +102,7 @@ icon="fa-pencil-square-o" name="open_partner_history" attrs="{'invisible': [('customer', '=', False)]}" - context="{'search_default_partner_id': active_id,'default_partner_id': active_id}"> + context="{'default_partner_id': active_id}">
Invoiced diff --git a/addons/account/views/web_planner_data.xml b/addons/account/views/web_planner_data.xml index ff796c2859b..de159a85cf0 100644 --- a/addons/account/views/web_planner_data.xml +++ b/addons/account/views/web_planner_data.xml @@ -447,7 +447,7 @@

Contracts & Subscriptions

Manage time & material contracts or fixed-price recurring subscriptions.

- Install Now diff --git a/addons/account_asset/models/account_asset.py b/addons/account_asset/models/account_asset.py index 7c5585b174b..942c54dfdf1 100644 --- a/addons/account_asset/models/account_asset.py +++ b/addons/account_asset/models/account_asset.py @@ -153,7 +153,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 @@ -174,7 +174,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 @@ -378,7 +378,7 @@ class AccountAssetAsset(models.Model): @api.multi def _compute_entries(self, date, group_entries=False): - depreciation_ids = self.env['account.asset.depreciation.line'].with_context(depreciation_date=date).search([ + depreciation_ids = self.env['account.asset.depreciation.line'].search([ ('asset_id', 'in', self.ids), ('depreciation_date', '<=', date), ('move_check', '=', False)]) if group_entries: diff --git a/addons/account_cancel/views/account_views.xml b/addons/account_cancel/views/account_views.xml index 41e8371efe1..54478d06960 100644 --- a/addons/account_cancel/views/account_views.xml +++ b/addons/account_cancel/views/account_views.xml @@ -53,7 +53,8 @@ {'reload_on_button': true} -