From ea53259d278d5f9bc6a9f285fed5a544a0b38823 Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Mon, 17 Oct 2016 11:54:19 +0200 Subject: [PATCH 1/9] [FIX] account_asset: inconsistencies deferred revenues and assets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The following configuration leads to different results: - Deferred revenue: 12 entries, 1/month, prorata temporis, 1000€, date 15th of October - Asset: 12 entries, 1/month, prorata temporis, 1000€, linear, date 15th of October Deferred revenues and assets should give identical results. opw-690034 --- addons/account_asset/account_asset.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/account_asset/account_asset.py b/addons/account_asset/account_asset.py index d5f726c6b36..856f4482e02 100644 --- a/addons/account_asset/account_asset.py +++ b/addons/account_asset/account_asset.py @@ -126,7 +126,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 @@ -147,7 +147,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 From 7e58821a9f842c95a88d41a8146f597a50020153 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Sun, 10 Jul 2016 16:29:08 +0200 Subject: [PATCH 2/9] [REF] account: prepare method for invoice tax lines This is needed for example, to enhance the analytic dimension of these lines. Closes #12739 opw-691337 --- addons/account/models/account_invoice.py | 29 ++++++++++++++++-------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index 44e72d8248a..8066ead0acb 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -540,6 +540,24 @@ 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'], + '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 = {} @@ -547,16 +565,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. From dca292b268bf21fc8191fae980428f5983d2164b Mon Sep 17 00:00:00 2001 From: Goffin Simon Date: Mon, 17 Oct 2016 17:03:57 +0200 Subject: [PATCH 3/9] [FIX] base_action_rule: Add followers in automated actions When registering a payment on an account invoice with an automated action which adds a follower "On Creation & Update", the function recompute was triggered(due to the field residual_company_signed) and the same followers was added two times for the invoice. The fix avoid to unlink and create the same followers several times(because it was the case with the function "_add_follower_command") opw:690939 --- addons/base_action_rule/base_action_rule.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py index 50456240725..1255dfb3be0 100644 --- a/addons/base_action_rule/base_action_rule.py +++ b/addons/base_action_rule/base_action_rule.py @@ -194,7 +194,11 @@ class base_action_rule(osv.osv): # subscribe followers if self.act_followers and hasattr(records, 'message_subscribe'): - records.message_subscribe(self.act_followers.ids) + followers = self.env['mail.followers'].sudo().search([ + ('res_model', '=', records._name), ('res_id', 'in', records.ids), + ('partner_id', 'in', self.act_followers.ids)]) + if not len(followers) == len(self.act_followers): + records.message_subscribe(self.act_followers.ids) # execute server actions if self.server_action_ids: From 327f88dfcee9a9b9855c0e1300e6db5797334f67 Mon Sep 17 00:00:00 2001 From: Wolfgang Taferner Date: Fri, 14 Oct 2016 15:14:25 +0200 Subject: [PATCH 4/9] [FIX] account: tax base computation Before the fix, the tax base amount is computed only for a complete invoice line. However, depending on the tax sequence and their inclusion in the base amount, the tax base amount might be different for the various taxes of an invoice line. We keep track of the base amount for every single tax, and refactor the `_compute_base_amount` to take these values into account. opw-691177 Closes #13819 --- addons/account/models/account.py | 4 ++ addons/account/models/account_invoice.py | 25 +++++--- .../tests/test_account_supplier_invoice.py | 62 +++++++++++++++++++ 3 files changed, 83 insertions(+), 8 deletions(-) diff --git a/addons/account/models/account.py b/addons/account/models/account.py index 7583f738316..7b8f7beafd1 100644 --- a/addons/account/models/account.py +++ b/addons/account/models/account.py @@ -717,6 +717,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 @@ -724,6 +727,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_invoice.py b/addons/account/models/account_invoice.py index 8066ead0acb..9beb4c0beb0 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 = { @@ -551,6 +554,7 @@ class AccountInvoice(models.Model): '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, @@ -580,6 +584,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 @@ -1293,15 +1298,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() From 8e720a9319317217fa39c3be982b41072fcd479a Mon Sep 17 00:00:00 2001 From: Joren Van Onder Date: Tue, 27 Sep 2016 16:00:27 +0200 Subject: [PATCH 5/9] [FIX] point_of_sale: delete validated orders immediately from db If you validate an order it will be sent to the backend. If you then close the POS frontend somehow before clicking 'Next order' this order will be reloaded from localStorage when reopening it. This is problematic because it allows the user to then modify this order and validate it again. Everything will appear fine but in the backend this new, modified order will be ignored as it has the same pos_reference as the one that was sent initially. The deeper underlying issue is that there exists a brief timeframe where the same order can exist twice in the db: both as 'order' and 'unpaid_order'. To ensure this does not happen this fix removes the order from the unpaid orders after it is saved as a validated order. Fixes #13538 --- addons/point_of_sale/static/src/js/db.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/addons/point_of_sale/static/src/js/db.js b/addons/point_of_sale/static/src/js/db.js index e836060df43..91c8db8302f 100644 --- a/addons/point_of_sale/static/src/js/db.js +++ b/addons/point_of_sale/static/src/js/db.js @@ -412,6 +412,10 @@ openerp.point_of_sale.load_db = function load_db(instance, module){ } } + // Only necessary when we store a new, validated order. Orders + // that where already stored should already have been removed. + this.remove_unpaid_order(order); + orders.push({id: order_id, data: order}); this.save('orders',orders); return order_id; From 8e094bb8fdc15a55e96d748cb4314e20492d2b10 Mon Sep 17 00:00:00 2001 From: Nils Hamerlinck Date: Tue, 18 Oct 2016 17:17:41 -0700 Subject: [PATCH 6/9] [FIX] point_of_sale: return raises an irrelevant warning When returning an item by entering an orderline with a negative quantity you got a warning message asking you to confirm a large payment amount. Introduced by 5a3d9ce9ce6d806c8433d27541673033ab13d905. Closes #13777 --- addons/point_of_sale/static/src/js/screens.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/point_of_sale/static/src/js/screens.js b/addons/point_of_sale/static/src/js/screens.js index a20b5ea5df4..2353059568f 100644 --- a/addons/point_of_sale/static/src/js/screens.js +++ b/addons/point_of_sale/static/src/js/screens.js @@ -1814,7 +1814,7 @@ openerp.point_of_sale.load_screens = function load_screens(instance, module){ // } // if the change is too large, it's probably an input error, make the user confirm. - if (!force_validation && (order.get_total_with_tax() * 1000 < order.get_total_paid())) { + if (!force_validation && order.get_total_with_tax() > 0 && (order.get_total_with_tax() * 1000 < order.get_total_paid())) { this.gui.show_popup('confirm',{ title: _t('Please Confirm Large Amount'), body: _t('Are you sure that the customer wants to pay') + From d3c84ed42c8c91a369f7a7fce187d66ba89f17df Mon Sep 17 00:00:00 2001 From: Goffin Simon Date: Tue, 18 Oct 2016 15:59:29 +0200 Subject: [PATCH 7/9] [FIX] account, account_cancel: visibility of the button_cancel_reconciliation When a account statement is validated, it's not possible to cancel a statement line. To cancel a line from a validated statement, you must first reset to new the statement. In this way, it's not possible to have a validated statement with unreconciled lines. opw:690766 --- addons/account/models/account_bank_statement.py | 1 + addons/account_cancel/account_cancel_view.xml | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py index 62c6f2a997a..3daacb8c348 100644 --- a/addons/account/models/account_bank_statement.py +++ b/addons/account/models/account_bank_statement.py @@ -373,6 +373,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_cancel/account_cancel_view.xml b/addons/account_cancel/account_cancel_view.xml index ee68bff12a1..3d6e85c8e22 100644 --- a/addons/account_cancel/account_cancel_view.xml +++ b/addons/account_cancel/account_cancel_view.xml @@ -54,7 +54,8 @@ {'reload_on_button': true} -