From 0edfc3fbc953859983139f225107e553acb7c55c Mon Sep 17 00:00:00 2001 From: Leonardo Pistone Date: Wed, 20 Aug 2014 18:00:53 +0200 Subject: [PATCH 001/218] yaml: use selected user and not admin for onchange Fixes: #1885 --- openerp/tools/yaml_import.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openerp/tools/yaml_import.py b/openerp/tools/yaml_import.py index b065294f7d7..96381a56372 100644 --- a/openerp/tools/yaml_import.py +++ b/openerp/tools/yaml_import.py @@ -447,7 +447,7 @@ class YamlInterpreter(object): # Evaluation args args = map(lambda x: eval(x, ctx), match.group(2).split(',')) - result = getattr(model, match.group(1))(self.cr, SUPERUSER_ID, [], *args) + result = getattr(model, match.group(1))(self.cr, self.uid, [], *args) for key, val in (result or {}).get('value', {}).items(): if key in fg: record_dict[key] = process_val(key, val) From 9b78f06e471cf5766828976cf091879c28aff4b2 Mon Sep 17 00:00:00 2001 From: Leonardo Pistone Date: Fri, 5 Sep 2014 17:30:15 +0200 Subject: [PATCH 002/218] yaml: use selected user and not admin for defaults --- openerp/tools/yaml_import.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openerp/tools/yaml_import.py b/openerp/tools/yaml_import.py index 96381a56372..264ea65cba1 100644 --- a/openerp/tools/yaml_import.py +++ b/openerp/tools/yaml_import.py @@ -393,7 +393,7 @@ class YamlInterpreter(object): fg = view_info['fields'] # gather the default values on the object. (Can't use `fields´ as parameter instead of {} because we may # have references like `base.main_company´ in the yaml file and it's not compatible with the function) - defaults = default and model._add_missing_default_values(self.cr, SUPERUSER_ID, {}, context=self.context) or {} + defaults = default and model._add_missing_default_values(self.cr, self.uid, {}, context=self.context) or {} # copy the default values in record_dict, only if they are in the view (because that's what the client does) # the other default values will be added later on by the create(). From 13d0934f34c2b4450f14bb32f4b97dd3b9c2022a Mon Sep 17 00:00:00 2001 From: vrenaville Date: Fri, 28 Nov 2014 14:01:50 +0100 Subject: [PATCH 003/218] [FIX] account: chart of account opening period selection If a special period is present, it must be selected fist Fixes #3946 --- addons/account/wizard/account_chart.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/account/wizard/account_chart.py b/addons/account/wizard/account_chart.py index 32b83a0a670..62f99c3c854 100644 --- a/addons/account/wizard/account_chart.py +++ b/addons/account/wizard/account_chart.py @@ -51,7 +51,7 @@ class account_chart(osv.osv_memory): FROM account_period p LEFT JOIN account_fiscalyear f ON (p.fiscalyear_id = f.id) WHERE f.id = %s - ORDER BY p.date_start ASC + ORDER BY p.date_start ASC, p.special DESC LIMIT 1) AS period_start UNION ALL SELECT * FROM (SELECT p.id From bfadcdd5d6cd7e2a081efcc5f537d80be3812be8 Mon Sep 17 00:00:00 2001 From: Julien Legros Date: Fri, 28 Nov 2014 13:39:37 +0100 Subject: [PATCH 004/218] [FIX] stock: compute average price on action_done The average price computation is now deduplicated and moved to a separate function called in stock_move action_done. This makes sure it is always called when a stock.move is processed, even without going through the partial picking wizard. Fixes #2991 Closes #3949 OPW-615491 --- addons/stock/stock.py | 114 ++++++++++++++++++------------------------ 1 file changed, 50 insertions(+), 64 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 7dc39c150a2..23c72379919 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -1233,8 +1233,6 @@ class stock_picking(osv.osv): context = dict(context) res = {} move_obj = self.pool.get('stock.move') - product_obj = self.pool.get('product.product') - currency_obj = self.pool.get('res.currency') uom_obj = self.pool.get('product.uom') sequence_obj = self.pool.get('ir.sequence') wf_service = netsvc.LocalService("workflow") @@ -1263,40 +1261,12 @@ class stock_picking(osv.osv): else: too_many.append(move) - # Average price computation if (pick.type == 'in') and (move.product_id.cost_method == 'average'): - product = product_obj.browse(cr, uid, move.product_id.id) - move_currency_id = move.company_id.currency_id.id - context['currency_id'] = move_currency_id - qty = uom_obj._compute_qty(cr, uid, product_uom, product_qty, product.uom_id.id) - - if product.id not in product_avail: - # keep track of stock on hand including processed lines not yet marked as done - product_avail[product.id] = product.qty_available - - if qty > 0: - new_price = currency_obj.compute(cr, uid, product_currency, - move_currency_id, product_price, round=False) - new_price = uom_obj._compute_price(cr, uid, product_uom, new_price, - product.uom_id.id) - if product_avail[product.id] <= 0: - product_avail[product.id] = 0 - new_std_price = new_price - else: - # Get the standard price - amount_unit = product.price_get('standard_price', context=context)[product.id] - new_std_price = ((amount_unit * product_avail[product.id])\ - + (new_price * qty))/(product_avail[product.id] + qty) - # Write the field according to price type field - product_obj.write(cr, uid, [product.id], {'standard_price': new_std_price}) - - # Record the values that were chosen in the wizard, so they can be - # used for inventory valuation if real-time valuation is enabled. - move_obj.write(cr, uid, [move.id], - {'price_unit': product_price, - 'price_currency_id': product_currency}) - - product_avail[product.id] += qty + # Record the values that were chosen in the wizard, so they can be + # used for average price computation and inventory valuation + move_obj.write(cr, uid, [move.id], + {'price_unit': product_price, + 'price_currency_id': product_currency}) # every line of the picking is empty, do not generate anything empty_picking = not any(q for q in move_product_qty.values() if q > 0) @@ -2343,6 +2313,44 @@ class stock_move(osv.osv): return reference_amount, reference_currency_id + def _update_average_price(self, cr, uid, move, context=None): + product_obj = self.pool.get('product.product') + currency_obj = self.pool.get('res.currency') + uom_obj = self.pool.get('product.uom') + product_avail = {} + + if (move.picking_id.type == 'in') and (move.product_id.cost_method == 'average'): + product = product_obj.browse(cr, uid, move.product_id.id) + move_currency_id = move.company_id.currency_id.id + context['currency_id'] = move_currency_id + + product_qty = move.product_qty + product_uom = move.product_uom.id + product_price = move.price_unit + product_currency = move.price_currency_id.id + + if product.id not in product_avail: + # keep track of stock on hand including processed lines not yet marked as done + product_avail[product.id] = product.qty_available + + qty = uom_obj._compute_qty(cr, uid, product_uom, product_qty, product.uom_id.id) + if qty > 0: + new_price = currency_obj.compute(cr, uid, product_currency, + move_currency_id, product_price, round=False) + new_price = uom_obj._compute_price(cr, uid, product_uom, new_price, + product.uom_id.id) + if product_avail[product.id] <= 0: + product_avail[product.id] = 0 + new_std_price = new_price + else: + # Get the standard price + amount_unit = product.price_get('standard_price', context=context)[product.id] + new_std_price = ((amount_unit * product_avail[product.id])\ + + (new_price * qty))/(product_avail[product.id] + qty) + + product_obj.write(cr, uid, [product.id],{'standard_price': new_std_price}) + + product_avail[product.id] += qty def _create_product_valuation_moves(self, cr, uid, move, context=None): """ @@ -2430,6 +2438,7 @@ class stock_move(osv.osv): if move.move_dest_id.auto_validate: self.action_done(cr, uid, [move.move_dest_id.id], context=context) + self._update_average_price(cr, uid, move, context=context) self._create_product_valuation_moves(cr, uid, move, context=context) if move.state not in ('confirmed','done','assigned'): todo.append(move.id) @@ -2692,9 +2701,6 @@ class stock_move(osv.osv): """ res = {} picking_obj = self.pool.get('stock.picking') - product_obj = self.pool.get('product.product') - currency_obj = self.pool.get('res.currency') - uom_obj = self.pool.get('product.uom') wf_service = netsvc.LocalService("workflow") if context is None: @@ -2721,33 +2727,13 @@ class stock_move(osv.osv): else: too_many.append(move) - # Average price computation if (move.picking_id.type == 'in') and (move.product_id.cost_method == 'average'): - product = product_obj.browse(cr, uid, move.product_id.id) - move_currency_id = move.company_id.currency_id.id - context['currency_id'] = move_currency_id - qty = uom_obj._compute_qty(cr, uid, product_uom, product_qty, product.uom_id.id) - if qty > 0: - new_price = currency_obj.compute(cr, uid, product_currency, - move_currency_id, product_price, round=False) - new_price = uom_obj._compute_price(cr, uid, product_uom, new_price, - product.uom_id.id) - if product.qty_available <= 0: - new_std_price = new_price - else: - # Get the standard price - amount_unit = product.price_get('standard_price', context=context)[product.id] - new_std_price = ((amount_unit * product.qty_available)\ - + (new_price * qty))/(product.qty_available + qty) - - product_obj.write(cr, uid, [product.id],{'standard_price': new_std_price}) - - # Record the values that were chosen in the wizard, so they can be - # used for inventory valuation if real-time valuation is enabled. - self.write(cr, uid, [move.id], - {'price_unit': product_price, - 'price_currency_id': product_currency, - }) + # Record the values that were chosen in the wizard, so they can be + # used for average price computation and inventory valuation + self.write(cr, uid, [move.id], + {'price_unit': product_price, + 'price_currency_id': product_currency, + }) for move in too_few: product_qty = move_product_qty[move.id] From 5f9280e854ac435bdcf903128d5e9d5d0b592070 Mon Sep 17 00:00:00 2001 From: Julien Legros Date: Fri, 28 Nov 2014 13:45:11 +0100 Subject: [PATCH 005/218] [ADD] purchase: average price computation test --- addons/purchase/tests/__init__.py | 26 +++ addons/purchase/tests/test_average_price.py | 177 ++++++++++++++++++++ 2 files changed, 203 insertions(+) create mode 100644 addons/purchase/tests/__init__.py create mode 100644 addons/purchase/tests/test_average_price.py diff --git a/addons/purchase/tests/__init__.py b/addons/purchase/tests/__init__.py new file mode 100644 index 00000000000..295471b9aa3 --- /dev/null +++ b/addons/purchase/tests/__init__.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Business Applications +# Copyright (c) 2012-TODAY OpenERP S.A. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +import test_average_price + +checks = [ + test_average_price, +] diff --git a/addons/purchase/tests/test_average_price.py b/addons/purchase/tests/test_average_price.py new file mode 100644 index 00000000000..4cc4849a33e --- /dev/null +++ b/addons/purchase/tests/test_average_price.py @@ -0,0 +1,177 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Business Applications +# Copyright (c) 2012-TODAY OpenERP S.A. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +#from openerp.test.common import TransactionCase +from datetime import date +from openerp.tests import common +from openerp import netsvc + + +class TestAveragePrice(common.TransactionCase): + def setUp(self): + super(TestAveragePrice, self).setUp() + cr, uid, context = self.cr, self.uid, {} + self.ir_model_data = self.registry('ir.model.data') + self.product_product = self.registry('product.product') + self.purchase_order = self.registry('purchase.order') + self.purchase_order_line = self.registry('purchase.order.line') + self.pricelist = self.registry('product.pricelist') + self.stock_location = self.registry('stock.location') + self.stock_picking = self.registry('stock.picking') + self.stock_move = self.registry('stock.move') + self.stock_partial_move = self.registry('stock.partial.move') + self.stock_partial_move_line = self.registry('stock.partial.move.line') + self.partial_picking = self.registry('stock.partial.picking') + self.partial_picking_line = self.registry('stock.partial.picking.line') + change_product_qty = self.registry('stock.change.product.qty') + + _, partner_id = self.registry('ir.model.data').get_object_reference(cr, uid, 'base', 'res_partner_1') + _, pricelist_id = self.registry('ir.model.data').get_object_reference(cr, uid, 'product', 'list0') + _, self.location_id = self.registry('ir.model.data').get_object_reference(cr, uid, 'stock', 'stock_location_stock') + _, self.supplier_location_id = self.registry('ir.model.data').get_object_reference(cr, uid, 'stock', 'stock_location_suppliers') + _, input_account_id = self.registry('ir.model.data').get_object_reference(cr, uid, 'account', 'xfa') + _, output_account_id = self.registry('ir.model.data').get_object_reference(cr, uid, 'account', 'xfa') + wf_service = netsvc.LocalService("workflow") + + self.standard_price = 10 + self.order_price_unit = 20 + self.available_qty = 1 + self.order_qty = 1 + self.picking_qty = 1 + + self.product_id = self.product_product.create(cr, uid, { + 'name': 'Average product', + 'cost_method': 'average', + 'valuation': 'real_time', + 'property_stock_account_input': input_account_id, + 'property_stock_account_output': output_account_id, + }, context=context) + + self.product_product.do_change_standard_price( + cr, uid, [self.product_id], { + 'new_price': self.standard_price, + 'stock_input_account': input_account_id, + 'stock_output_account': output_account_id}) + + change_product_qty_id = change_product_qty.create( + cr, uid, { + 'location_id': self.location_id, + 'new_quantity': self.available_qty, + 'product_id': self.product_id}) + change_product_qty.change_product_qty( + cr, uid, [change_product_qty_id], { + 'active_model': 'product.product', + 'active_id': self.product_id, + 'active_ids': [self.product_id]}) + + self.po_01_id = self.purchase_order.create(cr, uid, { + 'partner_id': partner_id, + 'location_id': self.location_id, + 'pricelist_id': pricelist_id, + }, context=context) + + self.order_line_10 = self.purchase_order_line.create(cr, uid, { + 'order_id': self.po_01_id, + 'product_id': self.product_id, + 'name': 'description', + 'date_planned': date.today(), + 'product_qty': self.order_qty, + 'price_unit': self.order_price_unit + }, context=context) + + wf_service.trg_validate(uid, 'purchase.order', self.po_01_id, 'purchase_confirm', cr) + + + def test_10_stock_move_action_done(self): + cr, uid, context = self.cr, self.uid, {} + picking_id = self.purchase_order.read(cr, uid, [self.po_01_id], ['picking_ids'])[0]['picking_ids'] + move_lines_ids = self.stock_picking.read(cr, uid, picking_id, ['move_lines'])[0]['move_lines'] + for move in self.stock_move.browse(cr, uid, move_lines_ids, context=context): + move.action_done() + + new_price = self.product_product.read(cr, uid, self.product_id, ['standard_price'], context=context)['standard_price'] + self.assertAlmostEqual( + new_price, + (self.available_qty * self.standard_price + self.order_qty * self.order_price_unit) + /(self.available_qty + self.order_qty)) + + def test_20_partial_stock_move(self): + cr, uid, context = self.cr, self.uid, {} + picking_ids = self.purchase_order.read(cr, uid, [self.po_01_id], ['picking_ids'])[0]['picking_ids'] + product = self.product_product.browse(cr, uid, self.product_id, context=context) + + partial_move_id = self.stock_partial_move.create(cr, uid, { + 'date': date.today(), + 'picking_id': picking_ids[0] + }, context=context) + + move_lines_ids = self.stock_picking.read(cr, uid, picking_ids, ['move_lines'])[0]['move_lines'] + for move in self.stock_move.browse(cr, uid, move_lines_ids, context=context): + self.stock_partial_move_line.create(cr, uid, { + 'product_id': self.product_id, + 'quantity': self.picking_qty, + 'product_uom': product.uom_id.id, + 'location_dest_id': self.location_id, + 'location_id': self.supplier_location_id, + 'move_id': move.id, + 'cost': self.order_price_unit, + 'wizard_id': partial_move_id, + }, context=context) + + self.stock_partial_move.do_partial(cr, uid, [partial_move_id], context=context) + + new_price = self.product_product.read(cr, uid, self.product_id, ['standard_price'], context=context)['standard_price'] + self.assertAlmostEqual( + new_price, + (self.available_qty * self.standard_price + self.order_qty * self.order_price_unit) + /(self.available_qty + self.order_qty)) + + def test_30_partial_stock_picking(self): + cr, uid, context = self.cr, self.uid, {} + picking_ids = self.purchase_order.read(cr, uid, [self.po_01_id], ['picking_ids'])[0]['picking_ids'] + product = self.product_product.browse(cr, uid, self.product_id, context=context) + + partial_picking_id = self.partial_picking.create(cr, uid, { + 'date': date.today(), + 'picking_id': picking_ids[0], + }, context=context) + + move_lines_ids = self.stock_picking.read(cr, uid, picking_ids, ['move_lines'])[0]['move_lines'] + for move in self.stock_move.browse(cr, uid, move_lines_ids, context=context): + self.partial_picking_line.create(cr, uid, { + 'product_id': self.product_id, + 'quantity': self.picking_qty, + 'product_uom': product.uom_id.id, + 'location_dest_id': self.location_id, + 'location_id': self.supplier_location_id, + 'move_id': move.id, + 'cost': self.order_price_unit, + 'wizard_id': partial_picking_id, + }, context=context) + + self.partial_picking.do_partial(cr, uid, [partial_picking_id], context=context) + + new_price = self.product_product.read(cr, uid, self.product_id, ['standard_price'], context=context)['standard_price'] + self.assertAlmostEqual( + new_price, + (self.available_qty * self.standard_price + self.order_qty * self.order_price_unit) + /(self.available_qty + self.order_qty)) + From bc2a52fb28a1ce690a148e87cf9bc7e0797ca4d0 Mon Sep 17 00:00:00 2001 From: Benoit Guillot Date: Mon, 1 Dec 2014 13:13:51 +0100 Subject: [PATCH 006/218] [IMP] mrp: add prepare method for manufacturing orders to allow override Fixes #3973 --- addons/mrp/procurement.py | 50 +++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/addons/mrp/procurement.py b/addons/mrp/procurement.py index b07f6f6de9d..0897aa4fd0f 100644 --- a/addons/mrp/procurement.py +++ b/addons/mrp/procurement.py @@ -25,6 +25,8 @@ from openerp.osv import fields from openerp.osv import osv from openerp.tools.translate import _ from openerp import netsvc +from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT + class procurement_order(osv.osv): _inherit = 'procurement.order' @@ -78,36 +80,44 @@ class procurement_order(osv.osv): res = procurement_obj.make_mo(cr, uid, ids, context=context) res = res.values() return len(res) and res[0] or 0 - + + def _get_date_planned(self, cr, uid, procurement, context=None): + format_date_planned = datetime.strptime(procurement.date_planned, + DEFAULT_SERVER_DATETIME_FORMAT) + date_planned = format_date_planned - relativedelta(days=procurement.product_id.produce_delay or 0.0) + date_planned = date_planned - relativedelta(days=procurement.company_id.manufacturing_lead) + return date_planned + + def _prepare_mo_vals(self, cr, uid, procurement, context=None): + res_id = procurement.move_id.id + newdate = self._get_date_planned(cr, uid, procurement, context=context) + return { + 'origin': procurement.origin, + 'product_id': procurement.product_id.id, + 'product_qty': procurement.product_qty, + 'product_uom': procurement.product_uom.id, + 'product_uos_qty': procurement.product_uos and procurement.product_uos_qty or False, + 'product_uos': procurement.product_uos and procurement.product_uos.id or False, + 'location_src_id': procurement.location_id.id, + 'location_dest_id': procurement.location_id.id, + 'bom_id': procurement.bom_id and procurement.bom_id.id or False, + 'date_planned': newdate.strftime(DEFAULT_SERVER_DATETIME_FORMAT), + 'move_prod_id': res_id, + 'company_id': procurement.company_id.id, + } + def make_mo(self, cr, uid, ids, context=None): """ Make Manufacturing(production) order from procurement @return: New created Production Orders procurement wise """ res = {} - company = self.pool.get('res.users').browse(cr, uid, uid, context).company_id production_obj = self.pool.get('mrp.production') move_obj = self.pool.get('stock.move') wf_service = netsvc.LocalService("workflow") procurement_obj = self.pool.get('procurement.order') for procurement in procurement_obj.browse(cr, uid, ids, context=context): - res_id = procurement.move_id.id - newdate = datetime.strptime(procurement.date_planned, '%Y-%m-%d %H:%M:%S') - relativedelta(days=procurement.product_id.produce_delay or 0.0) - newdate = newdate - relativedelta(days=company.manufacturing_lead) - produce_id = production_obj.create(cr, uid, { - 'origin': procurement.origin, - 'product_id': procurement.product_id.id, - 'product_qty': procurement.product_qty, - 'product_uom': procurement.product_uom.id, - 'product_uos_qty': procurement.product_uos and procurement.product_uos_qty or False, - 'product_uos': procurement.product_uos and procurement.product_uos.id or False, - 'location_src_id': procurement.location_id.id, - 'location_dest_id': procurement.location_id.id, - 'bom_id': procurement.bom_id and procurement.bom_id.id or False, - 'date_planned': newdate.strftime('%Y-%m-%d %H:%M:%S'), - 'move_prod_id': res_id, - 'company_id': procurement.company_id.id, - }) - + vals = self._prepare_mo_vals(cr, uid, procurement, context=context) + produce_id = production_obj.create(cr, uid, vals, context=context) res[procurement.id] = produce_id self.write(cr, uid, [procurement.id], {'state': 'running', 'production_id': produce_id}) bom_result = production_obj.action_compute(cr, uid, From a765876ffb946d52c2747617fdead3d3d7ffe172 Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Wed, 3 Dec 2014 14:57:56 +0100 Subject: [PATCH 007/218] [IMP] web: do not fully hide pager in grouped view When the list view is grouped, the page count should be hidden as irrelevant. However if it's fully hidden, the limit can no longer be changed. Instead of hidding the pager, this commit hides the arrows and replaces the content by the current limit to allow to be changed. --- addons/web/static/src/js/view_list.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js index 3a3e8d55848..600e049d1ff 100644 --- a/addons/web/static/src/js/view_list.js +++ b/addons/web/static/src/js/view_list.js @@ -1578,7 +1578,9 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we .filter(function (column) { return column.tag === 'field' }) .pluck('name').value(), function (groups) { - self.view.$pager.hide(); + // page count is irrelevant on grouped page, replace by limit + self.view.$pager.find('.oe_pager_group').hide(); + self.view.$pager.find('.oe_list_pager_state').text(self.view._limit ? self.view._limit : '∞'); $el[0].appendChild( self.render_groups(groups)); if (post_render) { post_render(); } From a300b6e3e75352b91689092b61b40e2491a16faf Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Wed, 3 Dec 2014 12:19:11 +0100 Subject: [PATCH 008/218] [FIX] delivery: display the label of the field number_of_packages Fixes #4011 --- addons/delivery/delivery_view.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/delivery/delivery_view.xml b/addons/delivery/delivery_view.xml index 5864b4c6653..fc0bae35aea 100644 --- a/addons/delivery/delivery_view.xml +++ b/addons/delivery/delivery_view.xml @@ -237,9 +237,9 @@
-
+ From 05de60cbf91e62ca03eee8fe91c0856dbd687941 Mon Sep 17 00:00:00 2001 From: Mart Raudsepp Date: Tue, 2 Dec 2014 13:02:18 +0000 Subject: [PATCH 009/218] [FIX] Correct floating number handling for _procure_orderpoint_confirm Fixes comparison with min_quantity orderpoint in scheduler - basic floating point math issue in procurement scheduler when comparing current quantity with orderpoint minimum quantity. In certain cases floating point comparison could result in e.g 400.0 < 400.0 == True due to typical floating point comparison issues, as Odoo doesn't use Decimal types where the issue doesn't exist. Fixes early exiting out of the loop cycle, in case qty is already near zero. Fixes the new procurement creation check, to not do that if it's close enough to zero already, to be considered a floating point math error, not really non-zero. These combined (or at least the last one) avoid each supply_method == buy pending in draft PO's getting a zero quantity extra procurement order each time the scheduler runs. Otherwise there could be hundreds of zero quantity procurement orders pending, which makes the confirming of the PO take hours, due to creating hundreds of stock moves for each order line. Use float_compare helper to solve all these with floating point type for now, instead of the more evasion possibility of converting to Decimal module. Two potential bad comparisons remain, add FIXME notes for now until further analysis. Also: Float rounding on reste when comparing and on the procurement qty --- addons/stock/procurement.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/addons/stock/procurement.py b/addons/stock/procurement.py index c105ed6c017..7b067ed7e3b 100644 --- a/addons/stock/procurement.py +++ b/addons/stock/procurement.py @@ -22,7 +22,7 @@ from openerp.osv import fields, osv from openerp.tools.translate import _ -from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT +from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT, float_compare, float_round from openerp import SUPERUSER_ID from dateutil.relativedelta import relativedelta from datetime import datetime @@ -352,21 +352,21 @@ class procurement_order(osv.osv): prods = self._product_virtual_get(cr, uid, op) if prods is None: continue - if prods < op.product_min_qty: + if float_compare(prods, op.product_min_qty, precision_rounding=op.product_uom.rounding) < 0: qty = max(op.product_min_qty, op.product_max_qty) - prods - reste = op.qty_multiple > 0 and qty % op.qty_multiple or 0.0 - if reste > 0: + if float_compare(reste, 0.0, precision_rounding=op.product_uom.rounding) > 0: qty += op.qty_multiple - reste - if qty <= 0: + if float_compare(qty, 0.0, precision_rounding=op.product_uom.rounding) <= 0: continue qty -= orderpoint_obj.subtract_procurements(cr, uid, op, context=context) - if qty > 0: + qty_rounded = float_round(qty, precision_rounding=op.product_uom.rounding) + if qty_rounded > 0: proc_id = procurement_obj.create(cr, uid, - self._prepare_orderpoint_procurement(cr, uid, op, qty, context=context), + self._prepare_orderpoint_procurement(cr, uid, op, qty_rounded, context=context), context=context) self.check(cr, uid, [proc_id]) self.run(cr, uid, [proc_id]) From 7a979b2d3e4dd012f129ca2911648ef33338323b Mon Sep 17 00:00:00 2001 From: Mart Raudsepp Date: Tue, 2 Dec 2014 13:26:17 +0000 Subject: [PATCH 010/218] [FIX] Fix floating point handling in purchase _prepare_order_line_move Without this better floating point handling, an extra stock move might be created for zero quantity for some order lines upon PO confirmation, because qty is equal to something closer to e.g 1.14e-13, but this is larger than 0, so it creates a stock.move, which gets rounded too late to 0.0 Closes #3346 --- addons/purchase/purchase.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py index 60fbac8a861..be0590059aa 100644 --- a/addons/purchase/purchase.py +++ b/addons/purchase/purchase.py @@ -756,7 +756,7 @@ class purchase_order(osv.osv): res.append(tmp) #if the order line has a bigger quantity than the procurement it was for (manually changed or minimal quantity), then #split the future stock move in two because the route followed may be different. - if diff_quantity > 0: + if float_compare(diff_quantity, 0.0, precision_rounding=order_line.product_uom.rounding) > 0: move_template['product_uom_qty'] = diff_quantity move_template['product_uos_qty'] = diff_quantity res.append(move_template) From def9222bd9af051f9739c3aa551c5d3a7e6c5999 Mon Sep 17 00:00:00 2001 From: Adrien Peiffer Date: Wed, 26 Nov 2014 15:23:34 +0100 Subject: [PATCH 011/218] [FIX] Fix wrong unit price on invoice genereted by stock picking when uos is used [IMP] Rounding not necessary --- addons/sale_stock/sale_stock.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/addons/sale_stock/sale_stock.py b/addons/sale_stock/sale_stock.py index d117659c123..1245b0b38f7 100644 --- a/addons/sale_stock/sale_stock.py +++ b/addons/sale_stock/sale_stock.py @@ -390,6 +390,8 @@ class stock_move(osv.osv): sale_line.order_id.partner_id, context=context)[sale_line.order_id.pricelist_id.id] else: res['price_unit'] = sale_line.price_unit + uos_coeff = move.product_uom_qty and move.product_uos_qty / move.product_uom_qty or 1.0 + res['price_unit'] = res['price_unit'] / uos_coeff return res From d6daf5fa2f3960d40b0fdf7e93365d918dade565 Mon Sep 17 00:00:00 2001 From: Cedric Snauwaert Date: Fri, 12 Apr 2013 15:08:56 +0200 Subject: [PATCH 012/218] [FIX] product: add store=dict to field name_template Recompute the name_template field if the template changes. backport of 41628fc + f25c5d8 to 7.0 plus some optimisations in triggers Fixes #3277 --- addons/product/product.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/addons/product/product.py b/addons/product/product.py index 69dc23b3fb6..98a33a98f8a 100644 --- a/addons/product/product.py +++ b/addons/product/product.py @@ -530,6 +530,10 @@ class product_product(osv.osv): def _set_image(self, cr, uid, id, name, value, args, context=None): return self.write(cr, uid, [id], {'image': tools.image_resize_image_big(value)}, context=context) + def _get_name_template_ids(self, cr, uid, ids, context=None): + template_ids = self.pool.get('product.product').search(cr, uid, [('product_tmpl_id', 'in', ids)]) + return list(set(template_ids)) + _defaults = { 'active': lambda *a: 1, 'price_extra': lambda *a: 0.0, @@ -561,7 +565,11 @@ class product_product(osv.osv): 'price_extra': fields.float('Variant Price Extra', digits_compute=dp.get_precision('Product Price')), 'price_margin': fields.float('Variant Price Margin', digits_compute=dp.get_precision('Product Price')), 'pricelist_id': fields.dummy(string='Pricelist', relation='product.pricelist', type='many2one'), - 'name_template': fields.related('product_tmpl_id', 'name', string="Template Name", type='char', size=128, store=True, select=True), + 'name_template': fields.related('product_tmpl_id', 'name', string="Template Name", type='char', size=128, store={ + 'product.template': (_get_name_template_ids, ['name'], 10), + 'product.product': (lambda self, cr, uid, ids, c=None: ids, ['product_tmpl_id'], 10), + + }, select=True), 'color': fields.integer('Color Index'), # image: all image fields are base64 encoded and PIL-supported 'image': fields.binary("Image", From 8eae018b278f12185455bf38c32d365ce55c9256 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Wed, 3 Dec 2014 15:12:01 +0100 Subject: [PATCH 013/218] [FIX] mrp: keep traceability of procurement.group Pass group_id information when a manufacturing order is created from a procurement to keep a better traceability and know the reason of stock.move creation. Fixes #4019 --- addons/mrp/mrp.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/addons/mrp/mrp.py b/addons/mrp/mrp.py index 5a257f29861..d1746dc705a 100644 --- a/addons/mrp/mrp.py +++ b/addons/mrp/mrp.py @@ -1041,7 +1041,8 @@ class mrp_production(osv.osv): source_location_id = production.product_id.property_stock_production.id destination_location_id = production.location_dest_id.id procs = proc_obj.search(cr, uid, [('production_id', '=', production.id)], context=context) - procurement_id = procs and procs[0] or False + procurement = procs and\ + proc_obj.browse(cr, uid, procs[0], context=context) or False data = { 'name': production.name, 'date': production.date_planned, @@ -1053,10 +1054,11 @@ class mrp_production(osv.osv): 'location_id': source_location_id, 'location_dest_id': destination_location_id, 'move_dest_id': production.move_prod_id.id, - 'procurement_id': procurement_id, + 'procurement_id': procurement and procurement.id, 'company_id': production.company_id.id, 'production_id': production.id, 'origin': production.name, + 'group_id': procurement and procurement.group_id.id, } move_id = stock_move.create(cr, uid, data, context=context) #a phantom bom cannot be used in mrp order so it's ok to assume the list returned by action_confirm @@ -1141,6 +1143,7 @@ class mrp_production(osv.osv): 'price_unit': product.standard_price, 'origin': production.name, 'warehouse_id': loc_obj.get_warehouse(cr, uid, production.location_src_id, context=context), + 'group_id': production.move_prod_id.group_id.id, }, context=context) if prev_move: From 31a01ea772eca493ede292c9390b0526d06ea836 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Tue, 25 Nov 2014 23:14:03 +0100 Subject: [PATCH 014/218] Fixes #3851 Create invoice from picking copies some parameters from the order to the invoice --- addons/purchase/stock.py | 10 ++++++++++ addons/sale_stock/sale_stock.py | 12 ++++++++++++ addons/stock_account/stock.py | 8 ++++---- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/addons/purchase/stock.py b/addons/purchase/stock.py index 9257c8d3942..22b335d225b 100644 --- a/addons/purchase/stock.py +++ b/addons/purchase/stock.py @@ -158,6 +158,16 @@ class stock_picking(osv.osv): invoice_line_obj.write(cr, uid, inv_lines, {'invoice_id': invoice_id}, context=context) return invoice_id + def _get_invoice_vals(self, cr, uid, key, inv_type, journal_id, move, context=None): + inv_vals = super(stock_picking, self)._get_invoice_vals(cr, uid, key, inv_type, journal_id, move, context=context) + if move.purchase_line_id and move.purchase_line_id.order_id: + purchase = move.purchase_line_id.order_id + inv_vals.update({ + 'fiscal_position': purchase.fiscal_position.id, + 'payment_term': purchase.payment_term_id.id, + }) + return inv_vals + class stock_warehouse(osv.osv): _inherit = 'stock.warehouse' diff --git a/addons/sale_stock/sale_stock.py b/addons/sale_stock/sale_stock.py index 1245b0b38f7..89bd7537d34 100644 --- a/addons/sale_stock/sale_stock.py +++ b/addons/sale_stock/sale_stock.py @@ -444,3 +444,15 @@ class stock_picking(osv.osv): created_lines = sale_line_obj.invoice_line_create(cr, uid, sale_line_ids, context=context) invoice_line_obj.write(cr, uid, created_lines, {'invoice_id': invoice_id}, context=context) return invoice_id + + def _get_invoice_vals(self, cr, uid, key, inv_type, journal_id, move, context=None): + inv_vals = super(stock_picking, self)._get_invoice_vals(cr, uid, key, inv_type, journal_id, move, context=context) + sale = move.picking_id.sale_id + if sale: + inv_vals.update({ + 'fiscal_position': sale.fiscal_position.id, + 'payment_term': sale.payment_term.id, + 'user_id': sale.user_id.id, + 'name': sale.client_order_ref or '', + }) + return inv_vals diff --git a/addons/stock_account/stock.py b/addons/stock_account/stock.py index 16483e76c26..4b28bff1c5b 100644 --- a/addons/stock_account/stock.py +++ b/addons/stock_account/stock.py @@ -250,7 +250,7 @@ class stock_picking(osv.osv): invoices += self._invoice_create_line(cr, uid, moves, journal_id, type, context=context) return invoices - def _get_invoice_vals(self, cr, uid, key, inv_type, journal_id, origin, context=None): + def _get_invoice_vals(self, cr, uid, key, inv_type, journal_id, move, context=None): if context is None: context = {} partner, currency_id, company_id, user_id = key @@ -261,7 +261,7 @@ class stock_picking(osv.osv): account_id = partner.property_account_payable.id payment_term = partner.property_supplier_payment_term.id or False return { - 'origin': origin, + 'origin': move.picking_id.name, 'date_invoice': context.get('date_inv', False), 'user_id': user_id, 'partner_id': partner.id, @@ -287,7 +287,7 @@ class stock_picking(osv.osv): if key not in invoices: # Get account and payment terms - invoice_vals = self._get_invoice_vals(cr, uid, key, inv_type, journal_id, origin, context=context) + invoice_vals = self._get_invoice_vals(cr, uid, key, inv_type, journal_id, move, context=context) invoice_id = self._create_invoice_from_picking(cr, uid, move.picking_id, invoice_vals, context=context) invoices[key] = invoice_id @@ -309,4 +309,4 @@ class stock_picking(osv.osv): res.update({'invoice_state': op.picking_id.invoice_state}) if op.linked_move_operation_ids: res.update({'price_unit': op.linked_move_operation_ids[-1].move_id.price_unit}) - return res \ No newline at end of file + return res From 7c49c6d79d2029023caad3394a791957f4637c85 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Wed, 3 Dec 2014 18:40:36 +0100 Subject: [PATCH 015/218] [FIX] purchase: do not use deprecated api --- addons/purchase/tests/test_average_price.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/addons/purchase/tests/test_average_price.py b/addons/purchase/tests/test_average_price.py index 4cc4849a33e..79d299a9080 100644 --- a/addons/purchase/tests/test_average_price.py +++ b/addons/purchase/tests/test_average_price.py @@ -22,7 +22,7 @@ #from openerp.test.common import TransactionCase from datetime import date from openerp.tests import common -from openerp import netsvc +from openerp import workflow class TestAveragePrice(common.TransactionCase): @@ -49,7 +49,6 @@ class TestAveragePrice(common.TransactionCase): _, self.supplier_location_id = self.registry('ir.model.data').get_object_reference(cr, uid, 'stock', 'stock_location_suppliers') _, input_account_id = self.registry('ir.model.data').get_object_reference(cr, uid, 'account', 'xfa') _, output_account_id = self.registry('ir.model.data').get_object_reference(cr, uid, 'account', 'xfa') - wf_service = netsvc.LocalService("workflow") self.standard_price = 10 self.order_price_unit = 20 @@ -97,8 +96,7 @@ class TestAveragePrice(common.TransactionCase): 'price_unit': self.order_price_unit }, context=context) - wf_service.trg_validate(uid, 'purchase.order', self.po_01_id, 'purchase_confirm', cr) - + workflow.trg_validate(uid, 'purchase.order', self.po_01_id, 'purchase_confirm', cr) def test_10_stock_move_action_done(self): cr, uid, context = self.cr, self.uid, {} From 80d4be0af9d9d1828a98435e44083aa975ffbbd1 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Wed, 3 Dec 2014 22:14:18 +0100 Subject: [PATCH 016/218] [FIX] Fixes #4026 on discount barcode in POS --- addons/point_of_sale/static/src/js/devices.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/point_of_sale/static/src/js/devices.js b/addons/point_of_sale/static/src/js/devices.js index 5e813b9d73c..b605e8ee884 100644 --- a/addons/point_of_sale/static/src/js/devices.js +++ b/addons/point_of_sale/static/src/js/devices.js @@ -695,7 +695,7 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal }; } - if(parse_result.type in {'product':'', 'weight':'', 'price':'', 'discount':''}){ //ean is associated to a product + if(parse_result.type in {'product':'', 'weight':'', 'price':''}){ //ean is associated to a product if(this.action_callback['product']){ this.action_callback['product'](parse_result); } From 04f3fffdb438ae3a9dd7ee46ac6d0f2e8a732cab Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Thu, 4 Dec 2014 10:20:55 +0100 Subject: [PATCH 017/218] Revert "[ADD] purchase: average price computation test" This reverts commit 5f9280e854ac435bdcf903128d5e9d5d0b592070. These tests have been introduced in 7.0/saas-3, but can no longer be applied in 8.0, as they uses models that do not exist anymore in the new wms. if such tests do not exist yet in Odoo 8.0, then these tests needs re-work, they cannot be applied like that Conflicts: addons/purchase/tests/test_average_price.py --- addons/purchase/tests/__init__.py | 26 --- addons/purchase/tests/test_average_price.py | 175 -------------------- 2 files changed, 201 deletions(-) delete mode 100644 addons/purchase/tests/__init__.py delete mode 100644 addons/purchase/tests/test_average_price.py diff --git a/addons/purchase/tests/__init__.py b/addons/purchase/tests/__init__.py deleted file mode 100644 index 295471b9aa3..00000000000 --- a/addons/purchase/tests/__init__.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Business Applications -# Copyright (c) 2012-TODAY OpenERP S.A. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## - -import test_average_price - -checks = [ - test_average_price, -] diff --git a/addons/purchase/tests/test_average_price.py b/addons/purchase/tests/test_average_price.py deleted file mode 100644 index 79d299a9080..00000000000 --- a/addons/purchase/tests/test_average_price.py +++ /dev/null @@ -1,175 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Business Applications -# Copyright (c) 2012-TODAY OpenERP S.A. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## - -#from openerp.test.common import TransactionCase -from datetime import date -from openerp.tests import common -from openerp import workflow - - -class TestAveragePrice(common.TransactionCase): - def setUp(self): - super(TestAveragePrice, self).setUp() - cr, uid, context = self.cr, self.uid, {} - self.ir_model_data = self.registry('ir.model.data') - self.product_product = self.registry('product.product') - self.purchase_order = self.registry('purchase.order') - self.purchase_order_line = self.registry('purchase.order.line') - self.pricelist = self.registry('product.pricelist') - self.stock_location = self.registry('stock.location') - self.stock_picking = self.registry('stock.picking') - self.stock_move = self.registry('stock.move') - self.stock_partial_move = self.registry('stock.partial.move') - self.stock_partial_move_line = self.registry('stock.partial.move.line') - self.partial_picking = self.registry('stock.partial.picking') - self.partial_picking_line = self.registry('stock.partial.picking.line') - change_product_qty = self.registry('stock.change.product.qty') - - _, partner_id = self.registry('ir.model.data').get_object_reference(cr, uid, 'base', 'res_partner_1') - _, pricelist_id = self.registry('ir.model.data').get_object_reference(cr, uid, 'product', 'list0') - _, self.location_id = self.registry('ir.model.data').get_object_reference(cr, uid, 'stock', 'stock_location_stock') - _, self.supplier_location_id = self.registry('ir.model.data').get_object_reference(cr, uid, 'stock', 'stock_location_suppliers') - _, input_account_id = self.registry('ir.model.data').get_object_reference(cr, uid, 'account', 'xfa') - _, output_account_id = self.registry('ir.model.data').get_object_reference(cr, uid, 'account', 'xfa') - - self.standard_price = 10 - self.order_price_unit = 20 - self.available_qty = 1 - self.order_qty = 1 - self.picking_qty = 1 - - self.product_id = self.product_product.create(cr, uid, { - 'name': 'Average product', - 'cost_method': 'average', - 'valuation': 'real_time', - 'property_stock_account_input': input_account_id, - 'property_stock_account_output': output_account_id, - }, context=context) - - self.product_product.do_change_standard_price( - cr, uid, [self.product_id], { - 'new_price': self.standard_price, - 'stock_input_account': input_account_id, - 'stock_output_account': output_account_id}) - - change_product_qty_id = change_product_qty.create( - cr, uid, { - 'location_id': self.location_id, - 'new_quantity': self.available_qty, - 'product_id': self.product_id}) - change_product_qty.change_product_qty( - cr, uid, [change_product_qty_id], { - 'active_model': 'product.product', - 'active_id': self.product_id, - 'active_ids': [self.product_id]}) - - self.po_01_id = self.purchase_order.create(cr, uid, { - 'partner_id': partner_id, - 'location_id': self.location_id, - 'pricelist_id': pricelist_id, - }, context=context) - - self.order_line_10 = self.purchase_order_line.create(cr, uid, { - 'order_id': self.po_01_id, - 'product_id': self.product_id, - 'name': 'description', - 'date_planned': date.today(), - 'product_qty': self.order_qty, - 'price_unit': self.order_price_unit - }, context=context) - - workflow.trg_validate(uid, 'purchase.order', self.po_01_id, 'purchase_confirm', cr) - - def test_10_stock_move_action_done(self): - cr, uid, context = self.cr, self.uid, {} - picking_id = self.purchase_order.read(cr, uid, [self.po_01_id], ['picking_ids'])[0]['picking_ids'] - move_lines_ids = self.stock_picking.read(cr, uid, picking_id, ['move_lines'])[0]['move_lines'] - for move in self.stock_move.browse(cr, uid, move_lines_ids, context=context): - move.action_done() - - new_price = self.product_product.read(cr, uid, self.product_id, ['standard_price'], context=context)['standard_price'] - self.assertAlmostEqual( - new_price, - (self.available_qty * self.standard_price + self.order_qty * self.order_price_unit) - /(self.available_qty + self.order_qty)) - - def test_20_partial_stock_move(self): - cr, uid, context = self.cr, self.uid, {} - picking_ids = self.purchase_order.read(cr, uid, [self.po_01_id], ['picking_ids'])[0]['picking_ids'] - product = self.product_product.browse(cr, uid, self.product_id, context=context) - - partial_move_id = self.stock_partial_move.create(cr, uid, { - 'date': date.today(), - 'picking_id': picking_ids[0] - }, context=context) - - move_lines_ids = self.stock_picking.read(cr, uid, picking_ids, ['move_lines'])[0]['move_lines'] - for move in self.stock_move.browse(cr, uid, move_lines_ids, context=context): - self.stock_partial_move_line.create(cr, uid, { - 'product_id': self.product_id, - 'quantity': self.picking_qty, - 'product_uom': product.uom_id.id, - 'location_dest_id': self.location_id, - 'location_id': self.supplier_location_id, - 'move_id': move.id, - 'cost': self.order_price_unit, - 'wizard_id': partial_move_id, - }, context=context) - - self.stock_partial_move.do_partial(cr, uid, [partial_move_id], context=context) - - new_price = self.product_product.read(cr, uid, self.product_id, ['standard_price'], context=context)['standard_price'] - self.assertAlmostEqual( - new_price, - (self.available_qty * self.standard_price + self.order_qty * self.order_price_unit) - /(self.available_qty + self.order_qty)) - - def test_30_partial_stock_picking(self): - cr, uid, context = self.cr, self.uid, {} - picking_ids = self.purchase_order.read(cr, uid, [self.po_01_id], ['picking_ids'])[0]['picking_ids'] - product = self.product_product.browse(cr, uid, self.product_id, context=context) - - partial_picking_id = self.partial_picking.create(cr, uid, { - 'date': date.today(), - 'picking_id': picking_ids[0], - }, context=context) - - move_lines_ids = self.stock_picking.read(cr, uid, picking_ids, ['move_lines'])[0]['move_lines'] - for move in self.stock_move.browse(cr, uid, move_lines_ids, context=context): - self.partial_picking_line.create(cr, uid, { - 'product_id': self.product_id, - 'quantity': self.picking_qty, - 'product_uom': product.uom_id.id, - 'location_dest_id': self.location_id, - 'location_id': self.supplier_location_id, - 'move_id': move.id, - 'cost': self.order_price_unit, - 'wizard_id': partial_picking_id, - }, context=context) - - self.partial_picking.do_partial(cr, uid, [partial_picking_id], context=context) - - new_price = self.product_product.read(cr, uid, self.product_id, ['standard_price'], context=context)['standard_price'] - self.assertAlmostEqual( - new_price, - (self.available_qty * self.standard_price + self.order_qty * self.order_price_unit) - /(self.available_qty + self.order_qty)) - From 35431de125a73a79f574dddb60409131179c01b5 Mon Sep 17 00:00:00 2001 From: Arthur Maniet Date: Thu, 4 Dec 2014 10:51:54 +0100 Subject: [PATCH 018/218] [FIX] res.currency: correctly compute the number of decimal places for currencies formatting in 'get_format_currencies_js_function'. --- openerp/addons/base/res/res_currency.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openerp/addons/base/res/res_currency.py b/openerp/addons/base/res/res_currency.py index 8a155d9a35e..3f9c8c54bda 100644 --- a/openerp/addons/base/res/res_currency.py +++ b/openerp/addons/base/res/res_currency.py @@ -276,7 +276,7 @@ class res_currency(osv.osv): That function expects the number as first parameter and the currency id as second parameter. In case of failure it returns undefined.""" function = "" for row in self.search_read(cr, uid, domain=[], fields=['id', 'name', 'symbol', 'rounding', 'position'], context=context): - digits = int(math.log10(1 / row['rounding'])) + digits = int(math.ceil(math.log10(1 / row['rounding']))) symbol = row['symbol'] or row['name'] format_number_str = "openerp.web.format_value(arguments[0], {type: 'float', digits: [69," + str(digits) + "]}, 0.00)" From 6629729f2de9c411a8fc4e9be10be0c0e4cb4c07 Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Thu, 4 Dec 2014 11:34:42 +0100 Subject: [PATCH 019/218] [FIX] account_analytic_analysis: tree fields access rights Add invoicing related fields on anlytic account tree view for the invoicing group only Otherwise, when a user not having the invoicing access rights displays the analytic account list, he gets an access right error. opw-619485 --- .../account_analytic_analysis/account_analytic_analysis_view.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/account_analytic_analysis/account_analytic_analysis_view.xml b/addons/account_analytic_analysis/account_analytic_analysis_view.xml index 125707192bf..3d7a2d3a9fb 100644 --- a/addons/account_analytic_analysis/account_analytic_analysis_view.xml +++ b/addons/account_analytic_analysis/account_analytic_analysis_view.xml @@ -168,6 +168,7 @@ account.analytic.account.list.contract account.analytic.account + From 9c7fb721f0859e0bc492c39a1bb704a97add3483 Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Thu, 4 Dec 2014 13:09:15 +0100 Subject: [PATCH 020/218] [FIX] translate: allow common english 2 chars terms Before, all isolated (between xml/html tags) two chars words coming from views were not translated (by choice). But, for some words, allowing them is useful. For instance, the word 'or' located between two buttons. opw-616716 --- openerp/tools/translate.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openerp/tools/translate.py b/openerp/tools/translate.py index 32fd5bcf6fe..3c9a8a2642e 100644 --- a/openerp/tools/translate.py +++ b/openerp/tools/translate.py @@ -137,6 +137,9 @@ _LOCALE2WIN32 = { } +# These are not all english small words, just those that could potentially be isolated within views +ENGLISH_SMALL_WORDS = set("as at by do go if in me no of ok on or to up us we".split()) + class UNIX_LINE_TERMINATOR(csv.excel): lineterminator = '\n' @@ -676,7 +679,7 @@ def trans_generate(lang, modules, cr): def push(mod, type, name, res_id, term): term = (term or '').strip() - if len(term) > 2: + if len(term) > 2 or term in ENGLISH_SMALL_WORDS: push_translation(mod, type, name, res_id, term) def get_root_view(xml_id): From aa10972d13eeb2414bcfb0a0c402ef49573e1756 Mon Sep 17 00:00:00 2001 From: Sandy Carter Date: Tue, 18 Nov 2014 09:33:47 -0500 Subject: [PATCH 021/218] Raise error on read of a browse object with bad id Check if id is valid by searching record columns when a key error is raised If the record has the column, the key error is actually an error on a missing or inaccessible id. Signed-off-by: Sandy Carter Closes #3658 --- openerp/osv/orm.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index dc949db16ad..7f064a401a8 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -501,7 +501,17 @@ class browse_record(object): def __getattr__(self, name): try: return self[name] - except KeyError, e: + except KeyError as e: + if name in self._all_columns: + raise ValueError( + 'Cannot fetch field "%(field)s" for "%(model)s" record ' + 'with ID %(id)s, that record does not exist or has been ' + 'deleted' % { + 'field': name, + 'model': self._model._name, + 'id': self._id, + } + ) raise AttributeError(e) def __contains__(self, name): From 4e9613609de96200d1347b243722400e915848a0 Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Thu, 4 Dec 2014 14:46:24 +0100 Subject: [PATCH 022/218] [FIX] sale: rounded sales team invoiced gauge The price_total field of the account invoice report is not rounded (it cannot be easily rounded, as this has to be done in the sql view) In a multi currencies environment, this is possible that the price_total value has a lot of digits We therefore round it manually, for the gauge of the sales team kanban view --- addons/sale/sales_team.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/addons/sale/sales_team.py b/addons/sale/sales_team.py index b6013838791..c782660d0ea 100644 --- a/addons/sale/sales_team.py +++ b/addons/sale/sales_team.py @@ -7,7 +7,7 @@ import json from openerp import tools from openerp.osv import fields, osv - +from openerp.tools.float_utils import float_repr class crm_case_section(osv.osv): _inherit = 'crm.case.section' @@ -37,7 +37,10 @@ class crm_case_section(osv.osv): res = {} for id in ids: created_domain = [('section_id', '=', id), ('state', 'not in', ['draft', 'cancel']), ('date', '>=', date_begin), ('date', '<=', date_end)] - res[id] = json.dumps(self.__get_bar_values(cr, uid, obj, created_domain, ['price_total', 'date'], 'price_total', 'date', context=context)) + values = self.__get_bar_values(cr, uid, obj, created_domain, ['price_total', 'date'], 'price_total', 'date', context=context) + for value in values: + value['value'] = float_repr(value.get('value', 0), precision_digits=self.pool['decimal.precision'].precision_get(cr, uid, 'Account')) + res[id] = json.dumps(values) return res _columns = { From 55fa50891d070c8aca0c3b8901477816a82e9139 Mon Sep 17 00:00:00 2001 From: Samus CTO Date: Tue, 25 Nov 2014 17:04:32 +0100 Subject: [PATCH 023/218] [FIX] Use _unknown model for missing models in relation fields During a migration of database, it is possible that some custom field ("x_", state is 'manual') are relational to model from a module that is not provided. Note: this used to work in Odoo 7.0 but crashed in 8.0. Closes #3877 --- openerp/addons/base/ir/ir_model.py | 7 +++++++ openerp/fields.py | 6 ++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/openerp/addons/base/ir/ir_model.py b/openerp/addons/base/ir/ir_model.py index c50044d3ff9..96819f918dc 100644 --- a/openerp/addons/base/ir/ir_model.py +++ b/openerp/addons/base/ir/ir_model.py @@ -62,6 +62,13 @@ def _in_modules(self, cr, uid, ids, field_name, arg, context=None): result[k] = ', '.join(sorted(installed_modules & set(xml_id.split('.')[0] for xml_id in v))) return result +class unknown(models.AbstractModel): + """ + Abstract model used as a substitute for relational fields with an unknown + comodel. + """ + _name = '_unknown' + class ir_model(osv.osv): _name = 'ir.model' _description = "Models" diff --git a/openerp/fields.py b/openerp/fields.py index 88a6fe8ef5f..df9cb9a3f36 100644 --- a/openerp/fields.py +++ b/openerp/fields.py @@ -1372,8 +1372,10 @@ class _Relational(Field): def _setup(self, env): super(_Relational, self)._setup(env) - assert self.comodel_name in env.registry, \ - "Field %s with unknown comodel_name %r" % (self, self.comodel_name) + if self.comodel_name not in env.registry: + _logger.warning("Field %s with unknown comodel_name %r" + % (self, self.comodel_name)) + self.comodel_name = '_unknown' @property def _related_domain(self): From ff3cb41d5cd5370acc9008d56d75a17e1686b2d8 Mon Sep 17 00:00:00 2001 From: sve-openerp Date: Fri, 5 Dec 2014 10:54:25 +0100 Subject: [PATCH 024/218] [FIX] purchase: remove redundancy in report The warehouse name and address name is often the same (name of the company). Remove the name of the address as two warehouse may use the same address. Fixes #4062 --- addons/purchase/views/report_purchaseorder.xml | 2 +- addons/purchase/views/report_purchasequotation.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/purchase/views/report_purchaseorder.xml b/addons/purchase/views/report_purchaseorder.xml index fd199a6db65..eaf7c57a95c 100644 --- a/addons/purchase/views/report_purchaseorder.xml +++ b/addons/purchase/views/report_purchaseorder.xml @@ -17,7 +17,7 @@
+ t-field-options='{"widget": "contact", "fields": ["address", "phone", "fax"], "no_marker": true}'/>

VAT:

diff --git a/addons/purchase/views/report_purchasequotation.xml b/addons/purchase/views/report_purchasequotation.xml index 60f1081eb06..aaf1c8175ac 100644 --- a/addons/purchase/views/report_purchasequotation.xml +++ b/addons/purchase/views/report_purchasequotation.xml @@ -17,7 +17,7 @@
+ t-field-options='{"widget": "contact", "fields": ["address", "phone", "fax"], "no_marker": true}'/>

VAT:

From 38124e7e3186d601a27e3d0288700a6c1664c202 Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Fri, 5 Dec 2014 13:48:35 +0100 Subject: [PATCH 025/218] [FIX] sale_stock: product_id_change_with_wh on sale order popup too product_id_change_with_wh onchange was not anymore called when using the popup form in sale order line. The behavior was different concerning the product onchange when being in the editable list or in the popup form For instance, for make to stock products not having enough stock, the warning was displayed when being in editable list, but not in the popup form This is a regression of rev. 86f785ae1b2741ab67384f3022b971ab638daeb8 opw-619624 --- addons/sale_stock/sale_stock_view.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/addons/sale_stock/sale_stock_view.xml b/addons/sale_stock/sale_stock_view.xml index 404316324f5..802d3a090a5 100644 --- a/addons/sale_stock/sale_stock_view.xml +++ b/addons/sale_stock/sale_stock_view.xml @@ -29,6 +29,12 @@ + + + Date: Fri, 5 Dec 2014 14:41:30 +0100 Subject: [PATCH 026/218] [FIX] purchase: replace company_id widget selection by option no_create For selection fields, name_get calls to resolve the display name are client-side initiated, and will display "Unknown" if the user does not have read access to the selected company. And the reason for using a selection widget in the first place was to prevent inadvertent creation of companies. This is now doable via the no_create option, so we can remove the selection widget. --- addons/purchase/purchase_view.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/addons/purchase/purchase_view.xml b/addons/purchase/purchase_view.xml index 62f7b282c68..301a702c71e 100644 --- a/addons/purchase/purchase_view.xml +++ b/addons/purchase/purchase_view.xml @@ -224,7 +224,7 @@ - + - + @@ -431,7 +431,7 @@ - + @@ -496,7 +496,7 @@ - + From 0bf69d6f82d94976a543816a552e173558d71084 Mon Sep 17 00:00:00 2001 From: qdp-odoo Date: Fri, 5 Dec 2014 15:53:30 +0100 Subject: [PATCH 027/218] [FIX] account: 'currency_id' field not defined on account.bank.statement object (correct name is 'currency') --- addons/account/account_view.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/account/account_view.xml b/addons/account/account_view.xml index e20b899ad11..ff6e8d5aae9 100644 --- a/addons/account/account_view.xml +++ b/addons/account/account_view.xml @@ -626,7 +626,7 @@