From a9ce4ffb225ac9e926ac2fd6ddf4467235ee016f Mon Sep 17 00:00:00 2001 From: Goffin Simon Date: Wed, 21 Oct 2015 16:31:18 +0200 Subject: [PATCH] [FIX] account, purchase, sale: included taxes Forward port of 503820acb6d82a85c0c49ac26f7e7f8cd73851dd from 8.0 opw:652310 --- addons/account/models/account.py | 2 +- addons/purchase/purchase.py | 22 ++--- addons/purchase/tests/__init__.py | 2 +- .../tests/test_onchange_product_id.py | 70 +++++++++++++ addons/sale/sale.py | 7 +- addons/sale/tests/test_product_id_change.py | 97 +++++++++++-------- 6 files changed, 145 insertions(+), 55 deletions(-) create mode 100644 addons/purchase/tests/test_onchange_product_id.py diff --git a/addons/account/models/account.py b/addons/account/models/account.py index 137befc14fd..cc2fba82e34 100644 --- a/addons/account/models/account.py +++ b/addons/account/models/account.py @@ -679,7 +679,7 @@ class AccountTax(models.Model): def _fix_tax_included_price(self, price, prod_taxes, line_taxes): """Subtract tax amount from price when corresponding "price included" taxes do not apply""" # FIXME get currency in param? - incl_tax = prod_taxes.filtered(lambda tax: tax.id not in line_taxes and tax.price_include) + incl_tax = prod_taxes.filtered(lambda tax: tax not in line_taxes and tax.price_include) if incl_tax: return incl_tax.compute_all(price)['total_excluded'] return price diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py index 6a6a329ee15..3d669a0cfaa 100644 --- a/addons/purchase/purchase.py +++ b/addons/purchase/purchase.py @@ -567,7 +567,10 @@ class PurchaseOrderLine(models.Model): date=self.order_id.date_order and self.order_id.date_order[:10], uom_id=self.product_uom) - price_unit = seller.price if seller else 0.0 + fpos = self.order_id.fiscal_position_id + self.taxes_id = fpos.map_tax(self.product_id.supplier_taxes_id) + + price_unit = self.env['account.tax']._fix_tax_included_price(seller.price, self.product_id.supplier_taxes_id, self.taxes_id) if seller else 0.0 if price_unit and seller and self.order_id.currency_id and seller.currency_id != self.order_id.currency_id: price_unit = seller.currency_id.compute(price_unit, self.order_id.currency_id) self.price_unit = price_unit @@ -582,9 +585,6 @@ class PurchaseOrderLine(models.Model): if product_lang.description_purchase: self.name += '\n' + product_lang.description_purchase - fpos = self.order_id.fiscal_position_id - self.taxes_id = fpos.map_tax(self.product_id.supplier_taxes_id) - class ProcurementRule(models.Model): _inherit = 'procurement.rule' @@ -624,7 +624,7 @@ class ProcurementOrder(models.Model): date=procurement.purchase_line_id.order_id.date_order and procurement.purchase_line_id.order_id.date_order[:10], uom_id=procurement.product_uom) - price_unit = seller.price if seller else 0.0 + price_unit = self.env['account.tax']._fix_tax_included_price(seller.price, procurement.purchase_line_id.product_id.supplier_taxes_id, procurement.purchase_line_id.taxes_id) if seller else 0.0 if price_unit and seller and procurement.purchase_line_id.order_id.currency_id and seller.currency_id != procurement.purchase_line_id.order_id.currency_id: price_unit = seller.currency_id.compute(price_unit, procurement.purchase_line_id.order_id.currency_id) @@ -697,7 +697,11 @@ class ProcurementOrder(models.Model): date=po.date_order and po.date_order[:10], uom_id=self.product_uom) - price_unit = seller.price if seller else 0.0 + taxes = self.product_id.supplier_taxes_id + fpos = po.fiscal_position_id + taxes_id = fpos.map_tax(taxes).ids if fpos else [] + + price_unit = self.env['account.tax']._fix_tax_included_price(seller.price, self.product_id.supplier_taxes_id, taxes_id) if seller else 0.0 if price_unit and seller and po.currency_id and seller.currency_id != po.currency_id: price_unit = seller.currency_id.compute(price_unit, po.currency_id) @@ -711,10 +715,6 @@ class ProcurementOrder(models.Model): date_planned = self.env['purchase.order.line']._get_date_planned(seller, po=po).strftime(DEFAULT_SERVER_DATETIME_FORMAT) - taxes = self.product_id.supplier_taxes_id - fpos = po.fiscal_position_id - taxes_id = fpos.map_tax(taxes).ids if fpos else [] - return { 'name': name, 'product_qty': self.product_qty, @@ -790,7 +790,7 @@ class ProcurementOrder(models.Model): date=po.date_order and po.date_order[:10], uom_id=self.product_uom) - price_unit = seller.price if seller else 0.0 + price_unit = self.env['account.tax']._fix_tax_included_price(seller.price, line.product_id.supplier_taxes_id, line.taxes_id) if seller else 0.0 if price_unit and seller and po.currency_id and seller.currency_id != po.currency_id: price_unit = seller.currency_id.compute(price_unit, po.currency_id) diff --git a/addons/purchase/tests/__init__.py b/addons/purchase/tests/__init__.py index 6ad38a5c324..28061bc088d 100644 --- a/addons/purchase/tests/__init__.py +++ b/addons/purchase/tests/__init__.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from . import test_purchase_order +from . import test_onchange_product_id, test_purchase_order \ No newline at end of file diff --git a/addons/purchase/tests/test_onchange_product_id.py b/addons/purchase/tests/test_onchange_product_id.py new file mode 100644 index 00000000000..cd1c1795f3c --- /dev/null +++ b/addons/purchase/tests/test_onchange_product_id.py @@ -0,0 +1,70 @@ +from datetime import datetime +from openerp.tests.common import TransactionCase +from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT + +class TestOnchangeProductId(TransactionCase): + """Test that when an included tax is mapped by a fiscal position, the included tax must be + subtracted to the price of the product. + """ + + def setUp(self): + super(TestOnchangeProductId, self).setUp() + self.fiscal_position_model = self.env['account.fiscal.position'] + self.fiscal_position_tax_model = self.env['account.fiscal.position.tax'] + self.tax_model = self.env['account.tax'] + self.po_model = self.env['purchase.order'] + self.po_line_model = self.env['purchase.order.line'] + self.res_partner_model = self.env['res.partner'] + self.product_tmpl_model = self.env['product.template'] + self.product_model = self.env['product.product'] + self.product_uom_model = self.env['product.uom'] + self.supplierinfo_model = self.env["product.supplierinfo"] + + def test_onchange_product_id(self): + + uom_id = self.product_uom_model.search([('name', '=', 'Unit(s)')])[0] + + partner_id = self.res_partner_model.create(dict(name="George")) + tax_include_id = self.tax_model.create(dict(name="Include tax", + amount='21.00', + price_include=True, + type_tax_use='purchase')) + tax_exclude_id = self.tax_model.create(dict(name="Exclude tax", + amount='0.00', + type_tax_use='purchase')) + supplierinfo_vals = { + 'name': partner_id.id, + 'price': 121.0, + } + + supplierinfo = self.supplierinfo_model.create(supplierinfo_vals) + + product_tmpl_id = self.product_tmpl_model.create(dict(name="Voiture", + list_price=121, + seller_ids=[(6, 0, [supplierinfo.id])], + supplier_taxes_id=[(6, 0, [tax_include_id.id])])) + product_id = self.product_model.create(dict(product_tmpl_id=product_tmpl_id.id)) + + fp_id = self.fiscal_position_model.create(dict(name="fiscal position", sequence=1)) + + fp_tax_id = self.fiscal_position_tax_model.create(dict(position_id=fp_id.id, + tax_src_id=tax_include_id.id, + tax_dest_id=tax_exclude_id.id)) + po_vals = { + 'partner_id': partner_id.id, + 'fiscal_position_id': fp_id.id, + 'order_line': [ + (0, 0, { + 'name': product_id.name, + 'product_id': product_id.id, + 'product_qty': 1.0, + 'product_uom': uom_id.id, + 'price_unit': 121.0, + 'date_planned': datetime.today().strftime(DEFAULT_SERVER_DATETIME_FORMAT), + })], + } + po = self.po_model.create(po_vals) + + po_line = po.order_line[0] + po_line.onchange_product_id() + self.assertEquals(100, po_line.price_unit, "The included tax must be subtracted to the price") diff --git a/addons/sale/sale.py b/addons/sale/sale.py index 0113fc8bf96..812c3d68e78 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -499,7 +499,6 @@ class SaleOrderLine(models.Model): line.price_reduce = line.price_subtotal / line.product_uom_qty if line.product_uom_qty else 0.0 @api.multi - @api.onchange('order_id', 'product_id') def _compute_tax_id(self): for line in self: fpos = line.order_id.fiscal_position_id or line.order_id.partner_id.property_account_position_id @@ -715,8 +714,10 @@ class SaleOrderLine(models.Model): name += '\n' + product.description_sale vals['name'] = name + self._compute_tax_id() + if self.order_id.pricelist_id and self.order_id.partner_id: - vals['price_unit'] = product.price + vals['price_unit'] = self.env['account.tax']._fix_tax_included_price(product.price, product.taxes_id, self.tax_id) self.update(vals) return {'domain': domain} @@ -734,7 +735,7 @@ class SaleOrderLine(models.Model): pricelist=self.order_id.pricelist_id.id, uom=self.product_uom.id ) - self.price_unit = product.price + self.price_unit = self.env['account.tax']._fix_tax_included_price(product.price, product.taxes_id, self.tax_id) @api.multi def unlink(self): diff --git a/addons/sale/tests/test_product_id_change.py b/addons/sale/tests/test_product_id_change.py index fd53b431235..ffac350cd5f 100644 --- a/addons/sale/tests/test_product_id_change.py +++ b/addons/sale/tests/test_product_id_change.py @@ -1,47 +1,66 @@ -import unittest from openerp.tests.common import TransactionCase -class TestProductIdChange(TransactionCase): +class TestOnchangeProductId(TransactionCase): """Test that when an included tax is mapped by a fiscal position, the included tax must be subtracted to the price of the product. """ def setUp(self): - super(TestProductIdChange, self).setUp() - self.fiscal_position_model = self.registry('account.fiscal.position') - self.fiscal_position_tax_model = self.registry('account.fiscal.position.tax') - self.tax_model = self.registry('account.tax') - self.pricelist_model = self.registry('product.pricelist') - self.res_partner_model = self.registry('res.partner') - self.product_tmpl_model = self.registry('product.template') - self.product_model = self.registry('product.product') - self.so_line_model = self.registry('sale.order.line') + super(TestOnchangeProductId, self).setUp() + self.fiscal_position_model = self.env['account.fiscal.position'] + self.fiscal_position_tax_model = self.env['account.fiscal.position.tax'] + self.tax_model = self.env['account.tax'] + self.so_model = self.env['sale.order'] + self.po_line_model = self.env['sale.order.line'] + self.res_partner_model = self.env['res.partner'] + self.product_tmpl_model = self.env['product.template'] + self.product_model = self.env['product.product'] + self.product_uom_model = self.env['product.uom'] + self.supplierinfo_model = self.env["product.supplierinfo"] + self.pricelist_model = self.env['product.pricelist'] - @unittest.skip(""" - Due to `sale` rewrite (94716a3f14d929574c46ef165e4364d12a5903ed), - the commit 503820acb6d82a85c0c49ac26f7e7f8cd73851dd has been partially ignored - during the forward-port to master and need to be rewritten using new-api. - """) - def test_product_id_change(self): - cr, uid = self.cr, self.uid - pricelist = self.pricelist_model.search(cr, uid, [('name', '=', 'Public Pricelist')])[0] - partner_id = self.res_partner_model.create(cr, uid, dict(name="George")) - tax_include_id = self.tax_model.create(cr, uid, dict(name="Include tax", - type='percent', - amount='0.21', - price_include=True)) - tax_exclude_id = self.tax_model.create(cr, uid, dict(name="Exclude tax", - type='percent', - amount='0.00')) - product_tmpl_id = self.product_tmpl_model.create(cr, uid, dict(name="Voiture", - list_price='121', - taxes_id=[(6, 0, [tax_include_id])])) - product_id = self.product_model.create(cr, uid, dict(product_tmpl_id=product_tmpl_id)) - fp_id = self.fiscal_position_model.create(cr, uid, dict(name="fiscal position", - sequence=1)) - fp_tax_id = self.fiscal_position_tax_model.create(cr, uid, dict(position_id=fp_id, - tax_src_id=tax_include_id, - tax_dest_id=tax_exclude_id)) - res = self.so_line_model.product_id_change(cr, uid, [], pricelist, product_id, partner_id=partner_id, - fiscal_position=fp_id) - self.assertEquals(100, res['value']['price_unit'], "The included tax must be subtracted to the price") + def test_onchange_product_id(self): + + uom_id = self.product_uom_model.search([('name', '=', 'Unit(s)')])[0] + pricelist = self.pricelist_model.search([('name', '=', 'Public Pricelist')])[0] + + partner_id = self.res_partner_model.create(dict(name="George")) + tax_include_id = self.tax_model.create(dict(name="Include tax", + amount='21.00', + price_include=True, + type_tax_use='sale')) + tax_exclude_id = self.tax_model.create(dict(name="Exclude tax", + amount='0.00', + type_tax_use='sale')) + + product_tmpl_id = self.product_tmpl_model.create(dict(name="Voiture", + list_price=121, + taxes_id=[(6, 0, [tax_include_id.id])])) + + product_id = self.product_model.create(dict(product_tmpl_id=product_tmpl_id.id)) + + fp_id = self.fiscal_position_model.create(dict(name="fiscal position", sequence=1)) + + fp_tax_id = self.fiscal_position_tax_model.create(dict(position_id=fp_id.id, + tax_src_id=tax_include_id.id, + tax_dest_id=tax_exclude_id.id)) + so_vals = { + 'partner_id': partner_id.id, + 'pricelist_id': pricelist.id, + 'fiscal_position_id': fp_id.id, + 'order_line': [ + (0, 0, { + 'name': product_id.name, + 'product_id': product_id.id, + 'product_uom_qty': 1.0, + 'product_uom': uom_id.id, + 'price_unit': 121.0 + }) + ] + } + + so = self.so_model.create(so_vals) + + so_line = so.order_line[0] + so_line.product_id_change() + self.assertEquals(100, so_line.price_unit, "The included tax must be subtracted to the price")