From ea53259d278d5f9bc6a9f285fed5a544a0b38823 Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Mon, 17 Oct 2016 11:54:19 +0200 Subject: [PATCH 01/14] [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 02/14] [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 a75961364b2c7055a044b87ceba435dc829ce8a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Mon, 17 Oct 2016 15:46:38 +0200 Subject: [PATCH 03/14] [FIX] web: properly apply styles attribute to form fields The form widgets did not apply the 'style' attribute to the result of the rendering. Because of that, many small adjustment were no longer possible. This commit makes sure that it is indeed done. --- addons/web/static/src/js/views/form_common.js | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/web/static/src/js/views/form_common.js b/addons/web/static/src/js/views/form_common.js index f869842415c..6752885f372 100644 --- a/addons/web/static/src/js/views/form_common.js +++ b/addons/web/static/src/js/views/form_common.js @@ -430,6 +430,7 @@ var FormWidget = Widget.extend(InvisibilityChangerMixin, { this.process_modifiers(); this._super(); this.$el.addClass(this.node.attrs["class"] || ""); + this.$el.attr('style', this.node.attrs.style); }, destroy: function() { $.fn.tooltip('destroy'); From dca292b268bf21fc8191fae980428f5983d2164b Mon Sep 17 00:00:00 2001 From: Goffin Simon Date: Mon, 17 Oct 2016 17:03:57 +0200 Subject: [PATCH 04/14] [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 05/14] [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 06/14] [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 07/14] [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 fad9378d17de346df000125f4fa7b60adb1992a2 Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Wed, 19 Oct 2016 10:57:47 +0200 Subject: [PATCH 08/14] [FIX] purchase_requisition: price unit in blanket order When the purchase tender of a PO refers to a blanket order, the price unit used to create/update the order lines doesn't take into account the potential difference of UoM. --- addons/purchase_requisition/purchase_requisition.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/addons/purchase_requisition/purchase_requisition.py b/addons/purchase_requisition/purchase_requisition.py index 9e26e38defd..cbda85865a9 100644 --- a/addons/purchase_requisition/purchase_requisition.py +++ b/addons/purchase_requisition/purchase_requisition.py @@ -229,10 +229,7 @@ class purchase_order(osv.osv): taxes_ids = line.product_id.supplier_taxes_id.filtered(lambda r: r.company_id == requisition.company_id).ids # Compute quantity and price_unit - if requisition.type_id.quantity_copy != 'copy': - product_qty = 0 - price_unit = line.price_unit - elif line.product_uom_id != line.product_id.uom_po_id: + if line.product_uom_id != line.product_id.uom_po_id: product_uom_obj = self.pool.get('product.uom') product_qty = product_uom_obj._compute_qty_obj( cr, uid, line.product_uom_id, line.product_qty, line.product_id.uom_po_id, context=context) @@ -241,6 +238,8 @@ class purchase_order(osv.osv): else: product_qty = line.product_qty price_unit = line.price_unit + if requisition.type_id.quantity_copy != 'copy': + product_qty = 0 # Compute price_unit in appropriate currency if requisition.company_id.currency_id != currency: From d8c88079d313a3c3912648e509743f71aa5f7126 Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Wed, 19 Oct 2016 11:01:03 +0200 Subject: [PATCH 09/14] [FIX] purchase_requisition: unit price when quantity change On a PO linked to a purchase tender, changing the quantity on a PO line will trigger a recompute of the unit price. However, this recomputation will take into account the unit price of the supplier pricelist, not the unit price on the purchase tender. opw-690483 --- .../purchase_requisition.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/addons/purchase_requisition/purchase_requisition.py b/addons/purchase_requisition/purchase_requisition.py index cbda85865a9..ee00441ffcb 100644 --- a/addons/purchase_requisition/purchase_requisition.py +++ b/addons/purchase_requisition/purchase_requisition.py @@ -2,6 +2,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from datetime import datetime +from openerp import api, models from openerp.osv import fields, osv from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT from openerp.tools.translate import _ @@ -279,6 +280,24 @@ class purchase_order(osv.osv): return res +class PurchaseOrderLine(models.Model): + _inherit = "purchase.order.line" + + @api.onchange('product_qty', 'product_uom') + def _onchange_quantity(self): + res = super(PurchaseOrderLine, self)._onchange_quantity() + if self.order_id.requisition_id: + for line in self.order_id.requisition_id.line_ids: + if line.product_id == self.product_id: + if line.product_uom_id != self.product_uom: + self.price_unit = self.env['product.uom']._compute_price( + line.product_uom_id.id, line.price_unit, to_uom_id=self.product_uom.id) + else: + self.price_unit = line.price_unit + break + return res + + class product_template(osv.osv): _inherit = 'product.template' _columns = { From d3e2aa5551403606087b3247edf4cf683ac02671 Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Wed, 19 Oct 2016 11:13:35 +0200 Subject: [PATCH 10/14] [FIX] purchase_requisition: incorrect method call Missing arguments in method call, which causes crash. --- addons/purchase_requisition/purchase_requisition.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/purchase_requisition/purchase_requisition.py b/addons/purchase_requisition/purchase_requisition.py index ee00441ffcb..970b388ac63 100644 --- a/addons/purchase_requisition/purchase_requisition.py +++ b/addons/purchase_requisition/purchase_requisition.py @@ -132,7 +132,8 @@ class purchase_requisition_line(osv.osv): for po in line.requisition_id.purchase_ids.filtered(lambda r: r.state in ['purchase', 'done']): for po_line in po.order_line.filtered(lambda r: r.product_id == line.product_id): if po_line.product_uom != line.product_uom_id: - total += self.pool.get('product.uom')._compute_qty_obj(po_line.product_uom, po_line.product_qty, line.product_uom_id) + total += self.pool.get('product.uom')._compute_qty_obj( + cr, uid, po_line.product_uom, po_line.product_qty, line.product_uom_id, context=context) else: total += po_line.product_qty result[line.id] = total From d3c84ed42c8c91a369f7a7fce187d66ba89f17df Mon Sep 17 00:00:00 2001 From: Goffin Simon Date: Tue, 18 Oct 2016 15:59:29 +0200 Subject: [PATCH 11/14] [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} -