Merge lp:~acsone-openerp/ocb-addons/7.0-bug-796570-amu into lp:ocb-addons

Proposed by Laurent Mignon (Acsone)
Status: Rejected
Rejected by: Holger Brunn (Therp)
Proposed branch: lp:~acsone-openerp/ocb-addons/7.0-bug-796570-amu
Merge into: lp:ocb-addons
Diff against target: 760 lines (+508/-43)
18 files modified
account/account_invoice.py (+2/-3)
account/product.py (+63/-0)
account/tests/__init__.py (+2/-0)
account/tests/test_default_tax.py (+162/-0)
delivery/sale.py (+3/-2)
delivery/stock.py (+5/-4)
hr_expense/hr_expense.py (+2/-10)
hr_timesheet_invoice/hr_timesheet_invoice.py (+6/-4)
mrp_repair/mrp_repair.py (+1/-1)
point_of_sale/point_of_sale.py (+0/-1)
purchase/purchase.py (+1/-2)
purchase/tests/__init__.py (+5/-0)
purchase/tests/test_default_tax.py (+106/-0)
purchase_requisition/purchase_requisition.py (+5/-2)
sale/sale.py (+1/-2)
sale/tests/__init__.py (+36/-0)
sale/tests/test_default_tax.py (+99/-0)
stock/stock.py (+9/-12)
To merge this branch: bzr merge lp:~acsone-openerp/ocb-addons/7.0-bug-796570-amu
Reviewer Review Type Date Requested Status
Holger Brunn (Therp) Disapprove
Review via email: mp+203378@code.launchpad.net

Description of the change

Automatically derived from https://code.launchpad.net/~acsone-openerp/openobject-addons/7.0-bug-796570-amu for https://code.launchpad.net/~openerp/openobject-addons/7.0. Below is a copy of the original description.

Fix the default tax bug lp:796570.

As requested by Fabien (https://bugs.launchpad.net/openobject-addons/+bug/796570/comments/5) the bug have been corrected on all the modules where the default tax is ignored (sale, purchase, delivery, mrp_repair, point_of_sale, purchase_requisition and stock).

Two functions have been added on the module 'product_product' (get_taxes_ids and get_supplier_taxes_ids). Those functions are now used in the corrected modules and in account, hr_expense and hr_timesheet_invoice in order to have the same logic everywhere.

To post a comment you must log in.
Revision history for this message
Holger Brunn (Therp) (hbrunn) wrote :

Thanks for your efforts!

I think what you did is perfectly fine, but the names and docstrings of the methods you introduce need improving: They pretend to return an id, while in fact they return a list of ids (which very often only has one element). So I'd rename the methods to get_tax_ids_or_default etc. and update the docstring accordingly. Sounds like nitpicking? I don't think so, for API functions it's very important that you get what you expect, and with a name phrased in singular, you expect a scalar, for a plural, you expect an iterable. And the fact that this doesn't apply to OpenERP's method/field names counts against their choice of names in the early days that we still have to live with now for legacy reasons, not for continuing doing this where it's not necessary.

Another thing is that I doubt '_or_default' needs to be part of the name. That should rather be part of the docstring.

Then if get_{supplier_,}taxes_or_default also accepted a list of ids, we could call it from a product's browse record, condensing the code a lot while making it more readable. Maybe add an assertion that the list can only contain one element.

review: Needs Fixing
Revision history for this message
Muschang Anthony (Acsone) (anthony-muschang) wrote :

Thank you for your review,

You are perfectly right. I'll add those clarifications in the next days.

> Thanks for your efforts!
>
> I think what you did is perfectly fine, but the names and docstrings of the
> methods you introduce need improving: They pretend to return an id, while in
> fact they return a list of ids (which very often only has one element). So I'd
> rename the methods to get_tax_ids_or_default etc. and update the docstring
> accordingly. Sounds like nitpicking? I don't think so, for API functions it's
> very important that you get what you expect, and with a name phrased in
> singular, you expect a scalar, for a plural, you expect an iterable. And the
> fact that this doesn't apply to OpenERP's method/field names counts against
> their choice of names in the early days that we still have to live with now
> for legacy reasons, not for continuing doing this where it's not necessary.
>
> Another thing is that I doubt '_or_default' needs to be part of the name. That
> should rather be part of the docstring.
>
> Then if get_{supplier_,}taxes_or_default also accepted a list of ids, we could
> call it from a product's browse record, condensing the code a lot while making
> it more readable. Maybe add an assertion that the list can only contain one
> element.

9891. By Anthony Muschang (Acsone)

[IMP]account: improve the docstring and method names of get_taxes_ids and get_supplier_taxes_ids

9892. By Anthony Muschang (Acsone)

[FIX]account: fix wrong calls to the methods and get_taxes_ids and get_supplier_taxes_ids

Revision history for this message
Muschang Anthony (Acsone) (anthony-muschang) wrote :

The new methods names and their doc-strings have been improved:

'get_taxes_id_or_default' is now called: 'get_taxes_ids',
'get_supplier_taxes_id_or_default' is now called: 'get_supplier_taxes_ids'.

Revision history for this message
Holger Brunn (Therp) (hbrunn) wrote :

#79, #49 I think you should do
assert len(ids) == 1
here

review: Needs Fixing (code review)
9893. By Anthony Muschang (Acsone)

[IMP]account: improve the assert in get_taxes_ids and in get_supplier_taxes_ids

Revision history for this message
Muschang Anthony (Acsone) (anthony-muschang) wrote :

Sorry for the delay.

The assert calls have been fixed.

Revision history for this message
Holger Brunn (Therp) (hbrunn) wrote :

thanks!

review: Approve (code review)
Revision history for this message
Holger Brunn (Therp) (hbrunn) wrote :

Development for 7.0 has moved to github on https://github.com/OCA/ocb - please move your merge proposal there if it is still valid.

(I close and reject this in order to have a cleaner overview for 6.1 MPs which indeed have to be done on launchpad)

review: Disapprove
Revision history for this message
St├ęphane Bidoul (Acsone) (sbi) wrote :

PR to official 7.0 is here https://github.com/odoo/odoo/pull/1353
Feel free to integrate it in OCB.

Unmerged revisions

9893. By Anthony Muschang (Acsone)

[IMP]account: improve the assert in get_taxes_ids and in get_supplier_taxes_ids

9892. By Anthony Muschang (Acsone)

[FIX]account: fix wrong calls to the methods and get_taxes_ids and get_supplier_taxes_ids

9891. By Anthony Muschang (Acsone)

[IMP]account: improve the docstring and method names of get_taxes_ids and get_supplier_taxes_ids

9890. By Anthony Muschang (Acsone)

[FIX] stock: if the tax is not provide on the product it will take from the product category

9889. By Anthony Muschang (Acsone)

[FIX] point_of_sale/hr_timesheet: if the tax is not provide on the product it will take from the product category

9888. By Anthony Muschang (Acsone)

[FIX] delivery/purchase_requisition/stock: if the tax is not provide on the product it will take from the product category

9887. By Anthony Muschang (Acsone)

[FIX] account/sale/purchase/mrp_repair/hr_expense: fix the implementation of the methods 'get_supplier_taxes_or_default' and 'get_taxes_or_default'

9886. By Anthony Muschang (Acsone)

[FIX] mrp_repair: if the tax is not provide on the product it will take from the product category

9885. By Anthony Muschang (Acsone)

[FIX] account/sale/purchase: fix the implementation of the methods 'get_supplier_taxes_or_default' and 'get_taxes_or_default'

9884. By Anthony Muschang (Acsone)

[FIX] account/sale/purchase: if the tax is not provide on the product it will take from the product category

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'account/account_invoice.py'
2--- account/account_invoice.py 2014-03-24 09:12:54 +0000
3+++ account/account_invoice.py 2014-03-25 13:20:47 +0000
4@@ -1513,10 +1513,9 @@
5 result['account_id'] = a
6
7 if type in ('out_invoice', 'out_refund'):
8- taxes = res.taxes_id and res.taxes_id or (a and self.pool.get('account.account').browse(cr, uid, a, context=context).tax_ids or False)
9+ tax_id = self.pool.get('product.product').get_taxes_ids(cr, uid, [res.id], fpos, context=context)
10 else:
11- taxes = res.supplier_taxes_id and res.supplier_taxes_id or (a and self.pool.get('account.account').browse(cr, uid, a, context=context).tax_ids or False)
12- tax_id = fpos_obj.map_tax(cr, uid, fpos, taxes)
13+ tax_id = self.pool.get('product.product').get_supplier_taxes_ids(cr, uid, [res.id], fpos, context=context)
14
15 if type in ('in_invoice', 'in_refund'):
16 result.update( {'price_unit': price_unit or res.standard_price,'invoice_line_tax_id': tax_id} )
17
18=== modified file 'account/product.py'
19--- account/product.py 2014-03-10 08:54:20 +0000
20+++ account/product.py 2014-03-25 13:20:47 +0000
21@@ -72,4 +72,67 @@
22
23 product_template()
24
25+
26+class product_product(osv.osv):
27+ _inherit = "product.product"
28+
29+ def get_taxes_ids(self, cr, uid, ids, fpos, context=None):
30+ """
31+ @param ids: The product ids list, must contains one element.
32+ @param fpos: The fiscal position
33+
34+ @return: the product taxes id list if there is one on the product,
35+ if there is none: return the product category default income tax,
36+ apply the fiscal position "fpos" on the returned taxes list.
37+ """
38+ taxes = self.get_taxes(cr, uid, ids, fpos, context=context)
39+ return self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
40+
41+ def get_taxes(self, cr, uid, ids, fpos, context=None):
42+ """
43+ @param ids: The product ids list, must contains one element.
44+ @param fpos: The fiscal position
45+
46+ @return: The product taxes list if there is one on the product,
47+ if there is none: return the product category default income tax
48+ """
49+ assert len(ids) == 1
50+ product = self.pool.get('product.product').browse(cr, uid, ids[0], context=context)
51+ taxes = product.taxes_id
52+ if not taxes:
53+ account = product.property_account_income or product.categ_id.property_account_income_categ
54+ account = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, account, context=context)
55+ taxes = account.tax_ids
56+ return taxes
57+
58+ def get_supplier_taxes_ids(self, cr, uid, ids, fpos, context=None):
59+ """
60+ @param ids: The product ids list, must contains one element.
61+ @param fpos: The fiscal position
62+
63+ @return: The product supplier taxes id list if there is one on the product,
64+ if there is none: return the product category default expense tax,
65+ apply the fiscal position "fpos" on the returned taxes list
66+ """
67+
68+ taxes = self.get_supplier_taxes(cr, uid, ids, fpos, context=context)
69+ return self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
70+
71+ def get_supplier_taxes(self, cr, uid, ids, fpos, context=None):
72+ """
73+ @param ids: The product ids list, must contains one element.
74+ @param fpos: The fiscal position
75+
76+ @return: The product supplier taxes list if there is one on the product,
77+ if there is none: return the product category default expense tax
78+ """
79+ assert len(ids) == 1
80+ product = self.pool.get('product.product').browse(cr, uid, ids[0], context=context)
81+ taxes = product.supplier_taxes_id
82+ if not taxes:
83+ account = product.property_account_expense or product.categ_id.property_account_expense_categ
84+ account = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, account, context=context)
85+ taxes = account.tax_ids
86+ return taxes
87+
88 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
89
90=== modified file 'account/tests/__init__.py'
91--- account/tests/__init__.py 2014-03-10 08:54:20 +0000
92+++ account/tests/__init__.py 2014-03-25 13:20:47 +0000
93@@ -1,7 +1,9 @@
94 from . import test_tax
95 from . import test_search
96+from . import test_default_tax
97
98 fast_suite = [
99 test_tax,
100 test_search,
101+ test_default_tax,
102 ]
103
104=== added file 'account/tests/test_default_tax.py'
105--- account/tests/test_default_tax.py 1970-01-01 00:00:00 +0000
106+++ account/tests/test_default_tax.py 2014-03-25 13:20:47 +0000
107@@ -0,0 +1,162 @@
108+# -*- coding: utf-8 -*-
109+##############################################################################
110+#
111+# Authors: Muschang Anthony
112+# Copyright (c) 2014 Acsone SA/NV (http://www.acsone.eu)
113+# All Rights Reserved
114+#
115+# WARNING: This program as such is intended to be used by professional
116+# programmers who take the whole responsibility of assessing all potential
117+# consequences resulting from its eventual inadequacies and bugs.
118+# End users who are looking for a ready-to-use solution with commercial
119+# guarantees and support are strongly advised to contact a Free Software
120+# Service Company.
121+#
122+# This program is free software: you can redistribute it and/or modify
123+# it under the terms of the GNU Affero General Public License as
124+# published by the Free Software Foundation, either version 3 of the
125+# License, or (at your option) any later version.
126+#
127+# This program is distributed in the hope that it will be useful,
128+# but WITHOUT ANY WARRANTY; without even the implied warranty of
129+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
130+# GNU Affero General Public License for more details.
131+#
132+# You should have received a copy of the GNU Affero General Public License
133+# along with this program. If not, see <http://www.gnu.org/licenses/>.
134+#
135+##############################################################################
136+import openerp.tests.common as common
137+import logging
138+
139+_logger = logging.getLogger(__name__)
140+
141+DB = common.DB
142+ADMIN_USER_ID = common.ADMIN_USER_ID
143+
144+
145+class test_default_tax(common.TransactionCase):
146+
147+ def test_default_tax_invoice(self):
148+ """
149+ Test obtaining the sale taxes from account.accout default tax
150+ """
151+
152+ account_model = self.registry('account.account')
153+ product_category_model = self.registry('product.category')
154+ product_model = self.registry('product.product')
155+ invoice_model = self.registry('account.invoice')
156+ invoice_line_model = self.registry('account.invoice.line')
157+ partner_model = self.registry('res.partner')
158+
159+ id_product_category_a = self.ref("product.product_category_1")
160+
161+ #add a tax id to the product category
162+ product_category = product_category_model.browse(self.cr, self.uid, id_product_category_a)
163+ id_account = product_category.property_account_income_categ.id
164+
165+ account_model.write(self.cr, ADMIN_USER_ID, id_account, {
166+ 'tax_ids': [(0, 0, {'name': 'Tax A'})],
167+ })
168+
169+ self.assertEquals(product_category.property_account_income_categ.tax_ids[0].name, "Tax A", "there must be a default tax on the product category")
170+
171+ id_product = product_model.create(self.cr, ADMIN_USER_ID, {
172+ 'name': 'Product A',
173+ 'categ_id': id_product_category_a,
174+ })
175+
176+ id_partner = partner_model.create(self.cr, ADMIN_USER_ID, {
177+ 'name': 'Partner A',
178+ })
179+
180+ id_invoice = invoice_model.create(self.cr, ADMIN_USER_ID, {
181+ 'partner_id': id_partner,
182+ 'account_id': id_account,
183+ })
184+
185+ invoice_model.write(self.cr, ADMIN_USER_ID, id_invoice, {
186+ 'invoice_line': [(0, 0, {'name': 'Order line A',
187+ 'invoice_id': id_invoice,
188+ })],
189+ })
190+
191+ invoice = invoice_model.browse(self.cr, self.uid, id_invoice)
192+ values_to_update = invoice_line_model.product_id_change(self.cr, self.uid, invoice.invoice_line[0].id, id_product, uom_id=False, partner_id=id_partner)['value']
193+ self.assertEqual(product_category.property_account_income_categ.tax_ids[0].id, values_to_update['invoice_line_tax_id'][0])
194+
195+ """Set an invoice account in the product, the default tax should be ignored"""
196+
197+ product_model.write(self.cr, ADMIN_USER_ID, id_product, {
198+ 'taxes_id': [(0, 0, {'name': 'Tax B'})],
199+ })
200+
201+ product = product_model.browse(self.cr, self.uid, id_product)
202+ self.assertEquals(product_category.property_account_income_categ.tax_ids[0].name, "Tax A", "the default tax on the product category must not have change")
203+ self.assertEquals(product.taxes_id[0].name, "Tax B", "the default tax on the product category must not have change")
204+
205+ values_to_update = invoice_line_model.product_id_change(self.cr, self.uid, invoice.invoice_line[0].id, id_product, uom_id=False, partner_id=id_partner)['value']
206+ self.assertEqual(product.taxes_id[0].id, values_to_update['invoice_line_tax_id'][0])
207+
208+ def test_default_tax_expense(self):
209+ """
210+ Test obtaining the purchase taxes from account.accout default tax
211+ """
212+
213+ account_model = self.registry('account.account')
214+ product_category_model = self.registry('product.category')
215+ product_model = self.registry('product.product')
216+ invoice_model = self.registry('account.invoice')
217+ invoice_line_model = self.registry('account.invoice.line')
218+ partner_model = self.registry('res.partner')
219+
220+ id_product_category_a = self.ref("product.product_category_1")
221+
222+ #add a tax id to the product category
223+ product_category = product_category_model.browse(self.cr, self.uid, id_product_category_a)
224+ id_account = product_category.property_account_expense_categ.id
225+
226+ account_model.write(self.cr, ADMIN_USER_ID, id_account, {
227+ 'tax_ids': [(0, 0, {'name': 'Tax A'})],
228+ })
229+
230+ self.assertEquals(product_category.property_account_expense_categ.tax_ids[0].name, "Tax A", "there must be a default tax on the product category")
231+
232+ id_product = product_model.create(self.cr, ADMIN_USER_ID, {
233+ 'name': 'Product A',
234+ 'categ_id': id_product_category_a,
235+ })
236+
237+ id_partner = partner_model.create(self.cr, ADMIN_USER_ID, {
238+ 'name': 'Partner A',
239+ })
240+
241+ id_invoice = invoice_model.create(self.cr, ADMIN_USER_ID, {
242+ 'partner_id': id_partner,
243+ 'account_id': id_account,
244+ })
245+
246+ invoice_model.write(self.cr, ADMIN_USER_ID, id_invoice, {
247+ 'invoice_line': [(0, 0, {'name': 'Order line A',
248+ 'invoice_id': id_invoice,
249+ })],
250+ })
251+
252+ invoice = invoice_model.browse(self.cr, self.uid, id_invoice)
253+ values_to_update = invoice_line_model.product_id_change(self.cr, self.uid, invoice.invoice_line[0].id, id_product, uom_id=False, partner_id=id_partner, type='in_invoice')['value']
254+ self.assertEqual(product_category.property_account_expense_categ.tax_ids[0].id, values_to_update['invoice_line_tax_id'][0])
255+
256+ """Set an invoice account in the product, the default tax should be ignored"""
257+
258+ product_model.write(self.cr, ADMIN_USER_ID, id_product, {
259+ 'supplier_taxes_id': [(0, 0, {'name': 'Tax B'})],
260+ })
261+
262+ product = product_model.browse(self.cr, self.uid, id_product)
263+ self.assertEquals(product_category.property_account_expense_categ.tax_ids[0].name, "Tax A", "the default tax on the product category must not have change")
264+ self.assertEquals(product.supplier_taxes_id[0].name, "Tax B", "the default tax on the product category must not have change")
265+
266+ values_to_update = invoice_line_model.product_id_change(self.cr, self.uid, invoice.invoice_line[0].id, id_product, uom_id=False, partner_id=id_partner, type='in_invoice')['value']
267+ self.assertEqual(product.supplier_taxes_id[0].id, values_to_update['invoice_line_tax_id'][0])
268+
269+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
270
271=== modified file 'delivery/sale.py'
272--- delivery/sale.py 2014-03-10 08:54:20 +0000
273+++ delivery/sale.py 2014-03-25 13:20:47 +0000
274@@ -48,6 +48,7 @@
275 grid_obj = self.pool.get('delivery.grid')
276 carrier_obj = self.pool.get('delivery.carrier')
277 acc_fp_obj = self.pool.get('account.fiscal.position')
278+ product_obj = self.pool.get('product.product')
279 for order in self.browse(cr, uid, ids, context=context):
280 grid_id = carrier_obj.grid_get(cr, uid, [order.carrier_id.id], order.partner_shipping_id.id)
281 if not grid_id:
282@@ -58,9 +59,9 @@
283
284 grid = grid_obj.browse(cr, uid, grid_id, context=context)
285
286- taxes = grid.carrier_id.product_id.taxes_id
287 fpos = order.fiscal_position or False
288- taxes_ids = acc_fp_obj.map_tax(cr, uid, fpos, taxes)
289+ taxes_ids = product_obj.get_taxes_ids(cr, uid, [grid.carrier_id.product_id.id], fpos, context=context)
290+
291 #create the sale order line
292 line_obj.create(cr, uid, {
293 'order_id': order.id,
294
295=== modified file 'delivery/stock.py'
296--- delivery/stock.py 2014-03-10 08:54:20 +0000
297+++ delivery/stock.py 2014-03-25 13:20:47 +0000
298@@ -86,6 +86,7 @@
299 """
300 carrier_obj = self.pool.get('delivery.carrier')
301 grid_obj = self.pool.get('delivery.grid')
302+ product_obj = self.pool.get('product.product')
303 if not picking.carrier_id or \
304 any(inv_line.product_id.id == picking.carrier_id.product_id.id
305 for inv_line in invoice.invoice_line):
306@@ -105,13 +106,13 @@
307 account_id = picking.carrier_id.product_id.categ_id\
308 .property_account_income_categ.id
309
310- taxes = picking.carrier_id.product_id.taxes_id
311 partner = picking.partner_id or False
312+
313 if partner:
314- account_id = self.pool.get('account.fiscal.position').map_account(cr, uid, partner.property_account_position, account_id)
315- taxes_ids = self.pool.get('account.fiscal.position').map_tax(cr, uid, partner.property_account_position, taxes)
316+ fpos = partner.property_account_position,
317 else:
318- taxes_ids = [x.id for x in taxes]
319+ fpos = False
320+ taxes_ids = product_obj.get_taxes_ids(cr, uid, [picking.carrier_id.product_id.id], fpos, context=context)
321
322 return {
323 'name': picking.carrier_id.name,
324
325=== modified file 'hr_expense/hr_expense.py'
326--- hr_expense/hr_expense.py 2014-03-24 09:12:54 +0000
327+++ hr_expense/hr_expense.py 2014-03-25 13:20:47 +0000
328@@ -303,7 +303,7 @@
329 res.append(mres)
330 current_product_line_pos = len(res) - 1
331 tax_code_found= False
332-
333+
334 #Calculate tax according to default tax on product
335 taxes = []
336 #Taken from product_id_onchange in account.invoice
337@@ -312,15 +312,7 @@
338 fpos_obj = self.pool.get('account.fiscal.position')
339 fpos = fposition_id and fpos_obj.browse(cr, uid, fposition_id, context=context) or False
340 product = line.product_id
341- taxes = product.supplier_taxes_id
342- #If taxes are not related to the product, maybe they are in the account
343- if not taxes:
344- a = product.property_account_expense.id #Why is not there a check here?
345- if not a:
346- a = product.categ_id.property_account_expense_categ.id
347- a = fpos_obj.map_account(cr, uid, fpos, a)
348- taxes = a and self.pool.get('account.account').browse(cr, uid, a, context=context).tax_ids or False
349- tax_id = fpos_obj.map_tax(cr, uid, fpos, taxes)
350+ taxes = self.pool.get('product.product').get_supplier_taxes(cr, uid, [product.id], fpos, context=context)
351 if not taxes:
352 continue
353 #Calculating tax on the line and creating move?
354
355=== modified file 'hr_timesheet_invoice/hr_timesheet_invoice.py'
356--- hr_timesheet_invoice/hr_timesheet_invoice.py 2014-03-10 08:54:20 +0000
357+++ hr_timesheet_invoice/hr_timesheet_invoice.py 2014-03-25 13:20:47 +0000
358@@ -263,15 +263,17 @@
359 if factor.customer_name:
360 factor_name += ' - ' + factor.customer_name
361
362+ fpos = account.partner_id.property_account_position
363 general_account = product.property_account_income or product.categ_id.property_account_income_categ
364+ general_account = fiscal_pos_obj.map_account(cr, uid, fpos, general_account)
365+
366 if not general_account:
367 raise osv.except_osv(_("Configuration Error!"), _("Please define income account for product '%s'.") % product.name)
368- taxes = product.taxes_id or general_account.tax_ids
369- tax = fiscal_pos_obj.map_tax(cr, uid, account.partner_id.property_account_position, taxes)
370+
371+ taxes = product_obj.get_taxes_ids(cr, uid, [product_id], fpos, context=context)
372 curr_line.update({
373- 'invoice_line_tax_id': [(6,0,tax )],
374 'name': factor_name,
375- 'invoice_line_tax_id': [(6,0,tax)],
376+ 'invoice_line_tax_id': [(6, 0, taxes)],
377 'account_id': general_account.id,
378 })
379 #
380
381=== modified file 'mrp_repair/mrp_repair.py'
382--- mrp_repair/mrp_repair.py 2014-03-24 09:12:54 +0000
383+++ mrp_repair/mrp_repair.py 2014-03-25 13:20:47 +0000
384@@ -587,7 +587,7 @@
385 product_obj = self.pool.get('product.product').browse(cr, uid, product)
386 if partner_id:
387 partner = self.pool.get('res.partner').browse(cr, uid, partner_id)
388- result['tax_id'] = self.pool.get('account.fiscal.position').map_tax(cr, uid, partner.property_account_position, product_obj.taxes_id)
389+ result['tax_id'] = self.pool.get('product.product').get_taxes_ids(cr, uid, [product], partner.property_account_position)
390
391 result['name'] = product_obj.partner_ref
392 result['product_uom'] = product_obj.uom_id and product_obj.uom_id.id or False
393
394=== modified file 'point_of_sale/point_of_sale.py'
395--- point_of_sale/point_of_sale.py 2014-03-24 09:12:54 +0000
396+++ point_of_sale/point_of_sale.py 2014-03-25 13:20:47 +0000
397@@ -896,7 +896,6 @@
398 inv_line['price_unit'] = line.price_unit
399 inv_line['discount'] = line.discount
400 inv_line['name'] = inv_name
401- inv_line['invoice_line_tax_id'] = [(6, 0, [x.id for x in line.product_id.taxes_id] )]
402 inv_line_ref.create(cr, uid, inv_line, context=context)
403 inv_ref.button_reset_taxes(cr, uid, [inv_id], context=context)
404 wf_service.trg_validate(uid, 'pos.order', order.id, 'invoice', cr)
405
406=== modified file 'purchase/purchase.py'
407--- purchase/purchase.py 2014-03-24 09:12:54 +0000
408+++ purchase/purchase.py 2014-03-25 13:20:47 +0000
409@@ -1030,9 +1030,8 @@
410 else:
411 price = product.standard_price
412
413- taxes = account_tax.browse(cr, uid, map(lambda x: x.id, product.supplier_taxes_id))
414 fpos = fiscal_position_id and account_fiscal_position.browse(cr, uid, fiscal_position_id, context=context) or False
415- taxes_ids = account_fiscal_position.map_tax(cr, uid, fpos, taxes)
416+ taxes_ids = product_product.get_supplier_taxes_ids(cr, uid, [product.id], fpos, context=context)
417 res['value'].update({'price_unit': price, 'taxes_id': taxes_ids})
418
419 return res
420
421=== added directory 'purchase/tests'
422=== added file 'purchase/tests/__init__.py'
423--- purchase/tests/__init__.py 1970-01-01 00:00:00 +0000
424+++ purchase/tests/__init__.py 2014-03-25 13:20:47 +0000
425@@ -0,0 +1,5 @@
426+from . import test_default_tax
427+
428+fast_suite = [
429+ test_default_tax,
430+]
431
432=== added file 'purchase/tests/test_default_tax.py'
433--- purchase/tests/test_default_tax.py 1970-01-01 00:00:00 +0000
434+++ purchase/tests/test_default_tax.py 2014-03-25 13:20:47 +0000
435@@ -0,0 +1,106 @@
436+# -*- coding: utf-8 -*-
437+##############################################################################
438+#
439+# Authors: Muschang Anthony
440+# Copyright (c) 2014 Acsone SA/NV (http://www.acsone.eu)
441+# All Rights Reserved
442+#
443+# WARNING: This program as such is intended to be used by professional
444+# programmers who take the whole responsibility of assessing all potential
445+# consequences resulting from its eventual inadequacies and bugs.
446+# End users who are looking for a ready-to-use solution with commercial
447+# guarantees and support are strongly advised to contact a Free Software
448+# Service Company.
449+#
450+# This program is free software: you can redistribute it and/or modify
451+# it under the terms of the GNU Affero General Public License as
452+# published by the Free Software Foundation, either version 3 of the
453+# License, or (at your option) any later version.
454+#
455+# This program is distributed in the hope that it will be useful,
456+# but WITHOUT ANY WARRANTY; without even the implied warranty of
457+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
458+# GNU Affero General Public License for more details.
459+#
460+# You should have received a copy of the GNU Affero General Public License
461+# along with this program. If not, see <http://www.gnu.org/licenses/>.
462+#
463+##############################################################################
464+import openerp.tests.common as common
465+import logging
466+import datetime
467+
468+_logger = logging.getLogger(__name__)
469+
470+DB = common.DB
471+ADMIN_USER_ID = common.ADMIN_USER_ID
472+
473+
474+class test_default_tax(common.TransactionCase):
475+
476+ def test_default_tax_expense(self):
477+ """
478+ Test obtaining the purchase taxes from account.accout default tax
479+ """
480+
481+ account_model = self.registry('account.account')
482+ product_category_model = self.registry('product.category')
483+ product_model = self.registry('product.product')
484+ purchase_model = self.registry('purchase.order')
485+ order_line_model = self.registry('purchase.order.line')
486+ partner_model = self.registry('res.partner')
487+
488+ id_product_category_a = self.ref("product.product_category_1")
489+
490+ #add a tax id to the product category
491+ product_category = product_category_model.browse(self.cr, self.uid, id_product_category_a)
492+ id_account = product_category.property_account_expense_categ.id
493+
494+ account_model.write(self.cr, ADMIN_USER_ID, id_account, {
495+ 'tax_ids': [(0, 0, {'name': 'Tax A'})],
496+ })
497+
498+ self.assertEquals(product_category.property_account_expense_categ.tax_ids[0].name, "Tax A", "there must be a default tax on the product category")
499+
500+ id_product = product_model.create(self.cr, ADMIN_USER_ID, {
501+ 'name': 'Product A',
502+ 'categ_id': id_product_category_a,
503+ })
504+
505+ id_partner = partner_model.create(self.cr, ADMIN_USER_ID, {
506+ 'name': 'Partner A',
507+ })
508+
509+ id_pricelist = self.ref("product.list0")
510+
511+ id_purchase = purchase_model.create(self.cr, ADMIN_USER_ID, {
512+ 'partner_id': id_partner,
513+ 'location_id': self.ref("stock.stock_location_3"),
514+ 'pricelist_id': id_pricelist,
515+ })
516+
517+ purchase_model.write(self.cr, ADMIN_USER_ID, id_purchase, {
518+ 'order_line': [(0, 0, {'name': 'Order line A',
519+ 'order_id': id_purchase,
520+ 'price_unit': 0.0,
521+ 'date_planned': datetime.date(2012, 12, 12)})],
522+ })
523+
524+ purchase = purchase_model.browse(self.cr, self.uid, id_purchase)
525+ values_to_update = order_line_model.product_id_change(self.cr, self.uid, purchase.order_line[0].id, id_pricelist, id_product, qty=1, uom_id=False, partner_id=id_partner)['value']
526+ self.assertEqual(product_category.property_account_expense_categ.tax_ids[0].id, values_to_update['taxes_id'][0])
527+
528+ """Set an purchase account in the product, the default tax should be ignored"""
529+
530+ product_model.write(self.cr, ADMIN_USER_ID, id_product, {
531+ 'supplier_taxes_id': [(0, 0, {'name': 'Tax B'})],
532+ })
533+
534+ product = product_model.browse(self.cr, self.uid, id_product)
535+ self.assertEquals(product_category.property_account_expense_categ.tax_ids[0].name, "Tax A", "the default tax on the product category must not have change")
536+ self.assertEquals(product.supplier_taxes_id[0].name, "Tax B", "the default tax on the product category must not have change")
537+
538+ values_to_update = order_line_model.product_id_change(self.cr, self.uid, purchase.order_line[0].id, id_pricelist, id_product, qty=1, uom_id=False, partner_id=id_partner)['value']
539+ self.assertEqual(product.supplier_taxes_id[0].id, values_to_update['taxes_id'][0])
540+
541+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
542
543=== modified file 'purchase_requisition/purchase_requisition.py'
544--- purchase_requisition/purchase_requisition.py 2014-03-10 08:54:20 +0000
545+++ purchase_requisition/purchase_requisition.py 2014-03-25 13:20:47 +0000
546@@ -157,6 +157,8 @@
547 purchase_order_line = self.pool.get('purchase.order.line')
548 res_partner = self.pool.get('res.partner')
549 fiscal_position = self.pool.get('account.fiscal.position')
550+ product_obj = self.pool.get('product.product')
551+
552 supplier = res_partner.browse(cr, uid, partner_id, context=context)
553 supplier_pricelist = supplier.property_product_pricelist_purchase or False
554 res = {}
555@@ -179,8 +181,9 @@
556 for line in requisition.line_ids:
557 product = line.product_id
558 seller_price, qty, default_uom_po_id, date_planned = self._seller_details(cr, uid, line, supplier, context=context)
559- taxes_ids = product.supplier_taxes_id
560- taxes = fiscal_position.map_tax(cr, uid, supplier.property_account_position, taxes_ids)
561+
562+ taxes = product_obj.get_supplier_taxes_ids(cr, uid, [product.id], supplier.property_account_position, context=context)
563+
564 purchase_order_line.create(cr, uid, {
565 'order_id': purchase_id,
566 'name': product.partner_ref,
567
568=== modified file 'sale/sale.py'
569--- sale/sale.py 2014-03-24 09:12:54 +0000
570+++ sale/sale.py 2014-03-25 13:20:47 +0000
571@@ -899,8 +899,7 @@
572 uos = False
573 fpos = fiscal_position and self.pool.get('account.fiscal.position').browse(cr, uid, fiscal_position) or False
574 if update_tax: #The quantity only have changed
575- result['tax_id'] = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, product_obj.taxes_id)
576-
577+ result['tax_id'] = self.pool.get('product.product').get_taxes_ids(cr, uid, [product_obj.id], fpos, context=context)
578 if not flag:
579 result['name'] = self.pool.get('product.product').name_get(cr, uid, [product_obj.id], context=context_partner)[0][1]
580 if product_obj.description_sale:
581
582=== added directory 'sale/tests'
583=== added file 'sale/tests/__init__.py'
584--- sale/tests/__init__.py 1970-01-01 00:00:00 +0000
585+++ sale/tests/__init__.py 2014-03-25 13:20:47 +0000
586@@ -0,0 +1,36 @@
587+# -*- coding: utf-8 -*-
588+##############################################################################
589+#
590+# Authors: Anthony Muschang
591+# Copyright (c) 2014 Acsone SA/NV (http://www.acsone.eu)
592+# All Rights Reserved
593+#
594+# WARNING: This program as such is intended to be used by professional
595+# programmers who take the whole responsibility of assessing all potential
596+# consequences resulting from its eventual inadequacies and bugs.
597+# End users who are looking for a ready-to-use solution with commercial
598+# guarantees and support are strongly advised to contact a Free Software
599+# Service Company.
600+#
601+# This program is free software: you can redistribute it and/or modify
602+# it under the terms of the GNU Affero General Public License as
603+# published by the Free Software Foundation, either version 3 of the
604+# License, or (at your option) any later version.
605+#
606+# This program is distributed in the hope that it will be useful,
607+# but WITHOUT ANY WARRANTY; without even the implied warranty of
608+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
609+# GNU Affero General Public License for more details.
610+#
611+# You should have received a copy of the GNU Affero General Public License
612+# along with this program. If not, see <http://www.gnu.org/licenses/>.
613+#
614+##############################################################################
615+import test_default_tax
616+
617+checks = [
618+ test_default_tax,
619+]
620+
621+
622+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
623
624=== added file 'sale/tests/test_default_tax.py'
625--- sale/tests/test_default_tax.py 1970-01-01 00:00:00 +0000
626+++ sale/tests/test_default_tax.py 2014-03-25 13:20:47 +0000
627@@ -0,0 +1,99 @@
628+# -*- coding: utf-8 -*-
629+##############################################################################
630+#
631+# Authors: Muschang Anthony
632+# Copyright (c) 2014 Acsone SA/NV (http://www.acsone.eu)
633+# All Rights Reserved
634+#
635+# WARNING: This program as such is intended to be used by professional
636+# programmers who take the whole responsibility of assessing all potential
637+# consequences resulting from its eventual inadequacies and bugs.
638+# End users who are looking for a ready-to-use solution with commercial
639+# guarantees and support are strongly advised to contact a Free Software
640+# Service Company.
641+#
642+# This program is free software: you can redistribute it and/or modify
643+# it under the terms of the GNU Affero General Public License as
644+# published by the Free Software Foundation, either version 3 of the
645+# License, or (at your option) any later version.
646+#
647+# This program is distributed in the hope that it will be useful,
648+# but WITHOUT ANY WARRANTY; without even the implied warranty of
649+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
650+# GNU Affero General Public License for more details.
651+#
652+# You should have received a copy of the GNU Affero General Public License
653+# along with this program. If not, see <http://www.gnu.org/licenses/>.
654+#
655+##############################################################################
656+import openerp.tests.common as common
657+import logging
658+
659+_logger = logging.getLogger(__name__)
660+
661+DB = common.DB
662+ADMIN_USER_ID = common.ADMIN_USER_ID
663+
664+
665+class test_default_tax(common.TransactionCase):
666+
667+ def setUp(self):
668+ super(test_default_tax, self).setUp()
669+
670+ def test_default_tax(self):
671+ """
672+ Test obtaining the sale and purchase taxes from account.accout default tax
673+ """
674+
675+ account_model = self.registry('account.account')
676+ product_category_model = self.registry('product.category')
677+ product_model = self.registry('product.product')
678+ sales_order_model = self.registry('sale.order')
679+ sales_order_line_model = self.registry('sale.order.line')
680+ partner_model = self.registry('res.partner')
681+
682+ id_product_category_a = self.ref("product.product_category_1")
683+
684+ #add a tax id to the product category
685+ product_category = product_category_model.browse(self.cr, self.uid, id_product_category_a)
686+
687+ account_model.write(self.cr, ADMIN_USER_ID, product_category.property_account_income_categ.id, {
688+ 'tax_ids': [(0, 0, {'name': 'Tax A'})],
689+ })
690+
691+ self.assertEquals(product_category.property_account_income_categ.tax_ids[0].name, "Tax A", "there must be a default tax on the product category")
692+
693+ id_product = product_model.create(self.cr, ADMIN_USER_ID, {
694+ 'name': 'Product A',
695+ 'categ_id': id_product_category_a,
696+ })
697+
698+ id_partner = partner_model.create(self.cr, ADMIN_USER_ID, {
699+ 'name': 'Partner A',
700+ })
701+
702+ id_pricelist = self.ref("product.list0")
703+
704+ id_sales_order = sales_order_model.create(self.cr, ADMIN_USER_ID, {
705+ 'partner_id': id_partner,
706+ 'pricelist_id': id_pricelist,
707+ 'partner_invoice_id': id_partner,
708+ 'partner_shipping_id': id_partner,
709+ })
710+
711+ sales_order_model.write(self.cr, ADMIN_USER_ID, id_sales_order, {
712+ 'order_line': [(0, 0, {'name': 'Order line A',
713+ 'product_id': id_product,
714+ 'product_uom_qty': 8,
715+ 'order_id': id_sales_order,
716+ })],
717+ })
718+
719+ sale_order = sales_order_model.browse(self.cr, self.uid, id_sales_order)
720+
721+ sales_order_values_to_update = sales_order_line_model.product_id_change(self.cr, self.uid, sale_order.order_line[0], id_pricelist, id_product, partner_id=id_partner)['value']
722+
723+ self.assertEqual(product_category.property_account_income_categ.tax_ids[0].id, sales_order_values_to_update['tax_id'][0])
724+
725+
726+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
727
728=== modified file 'stock/stock.py'
729--- stock/stock.py 2014-03-24 09:12:54 +0000
730+++ stock/stock.py 2014-03-25 13:20:47 +0000
731@@ -964,20 +964,17 @@
732 @param type: Type of invoice
733 @return: Taxes Ids for the move line
734 """
735- if type in ('in_invoice', 'in_refund'):
736- taxes = move_line.product_id.supplier_taxes_id
737- else:
738- taxes = move_line.product_id.taxes_id
739
740 if move_line.picking_id and move_line.picking_id.partner_id and move_line.picking_id.partner_id.id:
741- return self.pool.get('account.fiscal.position').map_tax(
742- cr,
743- uid,
744- move_line.picking_id.partner_id.property_account_position,
745- taxes
746- )
747- else:
748- return map(lambda x: x.id, taxes)
749+ fpos = move_line.picking_id.partner_id.property_account_position
750+ else:
751+ fpos = False
752+
753+ product_obj = self.pool.get('product.product')
754+ if type in ('in_invoice', 'in_refund'):
755+ return product_obj.get_supplier_taxes_ids(cr, uid, [move_line.product_id.id], fpos)
756+ else:
757+ return product_obj.get_taxes_ids(cr, uid, [move_line.product_id.id], fpos)
758
759 def _get_account_analytic_invoice(self, cr, uid, picking, move_line):
760 return False