diff --git a/addons/anonymization/anonymization.py b/addons/anonymization/anonymization.py index 8ecb74ee3ee..836ed3b8ad2 100644 --- a/addons/anonymization/anonymization.py +++ b/addons/anonymization/anonymization.py @@ -23,13 +23,10 @@ from lxml import etree import os import base64 -try: - import cPickle as pickle -except ImportError: - import pickle import random import datetime from openerp.osv import fields, osv +from openerp.tools import pickle from openerp.tools.translate import _ from openerp.tools.safe_eval import safe_eval as eval @@ -462,7 +459,7 @@ class ir_model_fields_anonymize_wizard(osv.osv_memory): # save pickle: fn = open(abs_filepath, 'w') - pickle.dump(data, fn, pickle.HIGHEST_PROTOCOL) + pickle.dump(data, fn, -1) # update the anonymization fields: values = { diff --git a/addons/auth_oauth/res_users.py b/addons/auth_oauth/res_users.py index 1aa86ba2d50..58198a24338 100644 --- a/addons/auth_oauth/res_users.py +++ b/addons/auth_oauth/res_users.py @@ -10,6 +10,9 @@ from openerp.addons.auth_signup.res_users import SignupError from openerp.osv import osv, fields from openerp import SUPERUSER_ID +from openerp.addons.base.res import res_users +res_users.USER_PRIVATE_FIELDS.append('oauth_access_token') + _logger = logging.getLogger(__name__) class res_users(osv.Model): diff --git a/addons/point_of_sale/tools/posbox/overwrite_after_init/etc/xdg/openbox/autostart b/addons/point_of_sale/tools/posbox/overwrite_after_init/etc/xdg/openbox/autostart index e2b61e2f591..15908b4c5aa 100755 --- a/addons/point_of_sale/tools/posbox/overwrite_after_init/etc/xdg/openbox/autostart +++ b/addons/point_of_sale/tools/posbox/overwrite_after_init/etc/xdg/openbox/autostart @@ -3,4 +3,11 @@ xset s off xset -dpms export HOME=/tmp -/usr/bin/firefox http://localhost:8069/point_of_sale/display & + +DISPLAY_URL='http://localhost:8069/point_of_sale/display' + +# wait until odoo is ready, otherwise you will have to plug a +# keyboard into the posbox just to refresh firefox +until wget -q "${DISPLAY_URL}" -O /dev/null ; do sleep 1 ; done + +/usr/bin/firefox "${DISPLAY_URL}" & diff --git a/addons/stock/stock.py b/addons/stock/stock.py index a179b75bf8d..8ecc96d5cf5 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -3039,35 +3039,64 @@ class stock_inventory_line(osv.osv): res['value']['product_qty'] = th_qty return res - def _resolve_inventory_line(self, cr, uid, inventory_line, context=None): - stock_move_obj = self.pool.get('stock.move') - quant_obj = self.pool.get('stock.quant') - diff = inventory_line.theoretical_qty - inventory_line.product_qty - if not diff: - return - #each theorical_lines where difference between theoretical and checked quantities is not 0 is a line for which we need to create a stock move - vals = { + # Do not forward port in 10.0 and beyond + def _get_move_values(self, cr, uid, inventory_line, qty, location_id, location_dest_id): + return { 'name': _('INV:') + (inventory_line.inventory_id.name or ''), 'product_id': inventory_line.product_id.id, 'product_uom': inventory_line.product_uom_id.id, + 'product_uom_qty': qty, 'date': inventory_line.inventory_id.date, 'company_id': inventory_line.inventory_id.company_id.id, 'inventory_id': inventory_line.inventory_id.id, 'state': 'confirmed', 'restrict_lot_id': inventory_line.prod_lot_id.id, 'restrict_partner_id': inventory_line.partner_id.id, - } + 'location_id': location_id, + 'location_dest_id': location_dest_id, + } + + def _fixup_negative_quants(self, cr, uid, inventory_line): + """ This will handle the irreconciable quants created by a force availability followed by a + return. When generating the moves of an inventory line, we look for quants of this line's + product created to compensate a force availability. If there are some and if the quant + which it is propagated from is still in the same location, we move it to the inventory + adjustment location before getting it back. Getting the quantity from the inventory + location will allow the negative quant to be compensated. + """ + quant_obj = self.pool.get('stock.quant') + stock_move_obj = self.pool.get('stock.move') + quant_ids = self._get_quants(cr, uid, inventory_line) + for quant in quant_obj.browse(cr, uid, quant_ids).filtered(lambda q: q.propagated_from_id.location_id.id == inventory_line.location_id.id): + # send the quantity to the inventory adjustment location + move_out_vals = self._get_move_values(cr, uid, inventory_line, quant.qty, inventory_line.location_id.id, inventory_line.product_id.property_stock_inventory.id) + move_out = stock_move_obj.create(cr, uid, move_out_vals) + move_out = stock_move_obj.browse(cr, uid, [move_out]) + quant_obj.quants_reserve(cr, uid, [(quant, quant.qty)], move_out) + move_out.action_done() + + # get back the quantity from the inventory adjustment location + move_in_vals = self._get_move_values(cr, uid, inventory_line, quant.qty, inventory_line.product_id.property_stock_inventory.id, inventory_line.location_id.id) + move_in = stock_move_obj.create(cr, uid, move_in_vals) + move_in = stock_move_obj.browse(cr, uid, [move_in]) + move_in.action_done() + + def _resolve_inventory_line(self, cr, uid, inventory_line, context=None): + stock_move_obj = self.pool.get('stock.move') + quant_obj = self.pool.get('stock.quant') + self._fixup_negative_quants(cr, uid, inventory_line) + + if float_compare(inventory_line.theoretical_qty, inventory_line.product_qty, precision_rounding=inventory_line.product_id.uom_id.rounding) == 0: + return False + diff = inventory_line.theoretical_qty - inventory_line.product_qty + + #each theorical_lines where difference between theoretical and checked quantities is not 0 is a line for which we need to create a stock move inventory_location_id = inventory_line.product_id.property_stock_inventory.id - if diff < 0: - #found more than expected - vals['location_id'] = inventory_location_id - vals['location_dest_id'] = inventory_line.location_id.id - vals['product_uom_qty'] = -diff + if diff < 0: # found more than expected + vals = self._get_move_values(cr, uid, inventory_line, abs(diff), inventory_location_id, inventory_line.location_id.id) else: - #found less than expected - vals['location_id'] = inventory_line.location_id.id - vals['location_dest_id'] = inventory_location_id - vals['product_uom_qty'] = diff + vals = self._get_move_values(cr, uid, inventory_line, abs(diff), inventory_line.location_id.id, inventory_location_id) + move_id = stock_move_obj.create(cr, uid, vals, context=context) move = stock_move_obj.browse(cr, uid, move_id, context=context) if diff > 0: diff --git a/addons/stock/tests/test_stock_flow.py b/addons/stock/tests/test_stock_flow.py index 09012465fe9..4bfa38d835c 100644 --- a/addons/stock/tests/test_stock_flow.py +++ b/addons/stock/tests/test_stock_flow.py @@ -1269,4 +1269,134 @@ class TestStockFlow(TestStockCommon): self.assertEqual(len(neg_quants), 0, 'There are negative quants!') # We should also make sure that when matching stock moves with pack operations, it takes the correct self.assertEqual(len(picking_out.move_lines[0].linked_move_operation_ids), 2, 'We should only have 2 links beween the move and the 2 operations') - self.assertEqual(len(picking_out.move_lines[0].quant_ids), 2, 'We should have exactly 2 quants in the end') \ No newline at end of file + self.assertEqual(len(picking_out.move_lines[0].quant_ids), 2, 'We should have exactly 2 quants in the end') + + # Do not forward port in 10.0 and beyond + def test_inventory_adjustment_and_negative_quants_1(self): + """Make sure negative quants from returns get wiped out with an inventory adjustment""" + productA = self.env['product.product'].create({'name': 'Product A', 'type': 'product'}) + stock_location = self.env.ref('stock.stock_location_stock') + customer_location = self.env.ref('stock.stock_location_customers') + location_loss = self.env.ref('stock.location_inventory') + + # Create a picking out and force availability + picking_out = self.env['stock.picking'].create({ + 'partner_id': self.env.ref('base.res_partner_2').id, + 'picking_type_id': self.env.ref('stock.picking_type_out').id, + 'location_id': stock_location.id, + 'location_dest_id': customer_location.id, + }) + self.env['stock.move'].create({ + 'name': productA.name, + 'product_id': productA.id, + 'product_uom_qty': 1, + 'product_uom': productA.uom_id.id, + 'picking_id': picking_out.id, + 'location_id': stock_location.id, + 'location_dest_id': customer_location.id, + }) + picking_out.action_confirm() + picking_out.force_assign() + picking_out.do_transfer() + + # Create return picking for all goods + default_data = self.env['stock.return.picking']\ + .with_context(active_ids=picking_out.ids, active_id=picking_out.ids[0])\ + .default_get([ + 'move_dest_exists', + 'product_return_moves' + ]) + + list_return_moves = default_data['product_return_moves'] + default_data['product_return_moves'] = [(0, 0, return_move) for return_move in list_return_moves] + + return_wiz = self.env['stock.return.picking']\ + .with_context(active_ids=picking_out.ids, active_id=picking_out.ids[0])\ + .create(default_data) + res = return_wiz._create_returns()[0] + return_pick = self.env['stock.picking'].browse(res) + return_pick.action_assign() + return_pick.do_transfer() + + # Make an inventory adjustment to set the quantity to 0 + inventory = self.env['stock.inventory'].create({ + 'name': 'Starting for product_1', + 'filter': 'product', + 'location_id': stock_location.id, + 'product_id': productA.id, + }) + inventory.prepare_inventory() + self.assertEqual(len(inventory.line_ids), 1, "Wrong inventory lines generated.") + self.assertEqual(inventory.line_ids.theoretical_qty, 0, "Theoretical quantity should be zero.") + inventory.action_done() + + # The inventory adjustment should have created two moves + self.assertEqual(len(inventory.move_ids), 2) + quantity = inventory.move_ids.mapped('product_qty') + self.assertEqual(quantity, [1, 1], "Moves created with wrong quantity.") + location_ids = inventory.move_ids.mapped('location_id').ids + self.assertEqual(set(location_ids), {stock_location.id, location_loss.id}) + + # There should be no quant in the stock location + quants = self.env['stock.quant'].search([('product_id', '=', productA.id), ('location_id', '=', stock_location.id)]) + self.assertEqual(len(quants), 0) + + # There should be one quant in the inventory loss location + quant = self.env['stock.quant'].search([('product_id', '=', productA.id), ('location_id', '=', location_loss.id)]) + self.assertEqual(len(quant), 1) + self.assertEqual(quant.qty, 1) + + def test_inventory_adjustment_and_negative_quants_2(self): + """Make sure negative quants get wiped out with an inventory adjustment""" + productA = self.env['product.product'].create({'name': 'Product A', 'type': 'product'}) + stock_location = self.env.ref('stock.stock_location_stock') + customer_location = self.env.ref('stock.stock_location_customers') + location_loss = self.env.ref('stock.location_inventory') + + # Create a picking out and force availability + picking_out = self.env['stock.picking'].create({ + 'partner_id': self.env.ref('base.res_partner_2').id, + 'picking_type_id': self.env.ref('stock.picking_type_out').id, + 'location_id': stock_location.id, + 'location_dest_id': customer_location.id, + }) + self.env['stock.move'].create({ + 'name': productA.name, + 'product_id': productA.id, + 'product_uom_qty': 1, + 'product_uom': productA.uom_id.id, + 'picking_id': picking_out.id, + 'location_id': stock_location.id, + 'location_dest_id': customer_location.id, + }) + picking_out.action_confirm() + picking_out.force_assign() + picking_out.do_transfer() + + # Make an inventory adjustment to set the quantity to 0 + inventory = self.env['stock.inventory'].create({ + 'name': 'Starting for product_1', + 'filter': 'product', + 'location_id': stock_location.id, + 'product_id': productA.id, + }) + inventory.prepare_inventory() + self.assertEqual(len(inventory.line_ids), 1, "Wrong inventory lines generated.") + self.assertEqual(inventory.line_ids.theoretical_qty, -1, "Theoretical quantity should be -1.") + inventory.line_ids.product_qty = 0 # Put the quantity back to 0 + inventory.action_done() + + # The inventory adjustment should have created one + self.assertEqual(len(inventory.move_ids), 1) + quantity = inventory.move_ids.mapped('product_qty') + self.assertEqual(quantity, [1], "Moves created with wrong quantity.") + location_ids = inventory.move_ids.mapped('location_id').ids + self.assertEqual(set(location_ids), {location_loss.id}) + + # There should be no quant in the stock location + quants = self.env['stock.quant'].search([('product_id', '=', productA.id), ('location_id', '=', stock_location.id)]) + self.assertEqual(len(quants), 0) + + # There should be no quant in the inventory loss location + quant = self.env['stock.quant'].search([('product_id', '=', productA.id), ('location_id', '=', location_loss.id)]) + self.assertEqual(len(quant), 0) diff --git a/openerp/http.py b/openerp/http.py index 255a530d695..ae6bea067fc 100644 --- a/openerp/http.py +++ b/openerp/http.py @@ -162,6 +162,8 @@ def redirect_with_hash(url, code=303): # See extensive test page at http://greenbytes.de/tech/tc/httpredirects/ if request.httprequest.user_agent.browser in ('firefox',): return werkzeug.utils.redirect(url, code) + if urlparse.urlparse(url, scheme='http').scheme not in ('http', 'https'): + url = 'http://' + url url = url.replace("'", "%27").replace("<", "%3C") return "" % url diff --git a/openerp/sql_db.py b/openerp/sql_db.py index bee4161c47e..a422e0f79f0 100644 --- a/openerp/sql_db.py +++ b/openerp/sql_db.py @@ -63,6 +63,19 @@ psycopg2.extensions.register_type(psycopg2.extensions.new_type((700, 701, 1700,) import tools + +from tools import parse_version as pv +if pv(psycopg2.__version__) < pv('2.7'): + from psycopg2._psycopg import QuotedString + def adapt_string(adapted): + """Python implementation of psycopg/psycopg2#459 from v2.7""" + if '\x00' in adapted: + raise ValueError("A string literal cannot contain NUL (0x00) characters.") + return QuotedString(adapted) + + psycopg2.extensions.register_adapter(str, adapt_string) + psycopg2.extensions.register_adapter(unicode, adapt_string) + from tools.func import frame_codeinfo from datetime import datetime as mdt from datetime import timedelta