From 9f31e50ee944934a2e2cae6a92491ecc400cdde1 Mon Sep 17 00:00:00 2001 From: Joren Van Onder Date: Tue, 13 Jun 2017 21:19:46 -0700 Subject: [PATCH 1/6] [FIX] point_of_sale: don't launch Firefox before Odoo has started When using Wi-Fi the Odoo instance on the POSBox will start after a Wi-Fi connection is established. This takes a bit longer than when you're using a wired connection. If Firefox gets started before you end up with a 'Could not connect to localhost:8069' error. --- .../overwrite_after_init/etc/xdg/openbox/autostart | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) 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}" & From 46263eb398443f2e19c737c4488f43990f244085 Mon Sep 17 00:00:00 2001 From: "Lucas Perais (lpe)" Date: Tue, 23 May 2017 14:09:16 +0200 Subject: [PATCH 2/6] [FIX] stock: backport fix negative quants Original commit (in 10.0) be9dce625c55e1b2d6039573c7035d61f762edc8 From original commit: It is still possible to have negative and positive quants in the same location because of returns: if you send something to the customer that is not there and you return it, you will still be able to reserve the returned goods to send to another client. Before, if you would do an inventory adjustment, it would not take into account these returned quants and their negative counterpart, which made them difficult to get out of the system. This fix takes them into account by creating two movements for one inventory line: move the positive counterpart to the inventory location before getting back from this location the same quantity. This way, even if you have 0 as quantity on hand but you have those 2 quants, it will eliminate them. (if you are increasing the stock, part of the process might have done it automatically already). Also, a key of context has been added which authorizes the process described above in the case of both a tracked product and no lot_id on the stock inventory OPW 743107 Closes #17167 --- addons/stock/stock.py | 65 +++++++++---- addons/stock/tests/test_stock_flow.py | 132 +++++++++++++++++++++++++- 2 files changed, 178 insertions(+), 19 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index a5959536d90..59ece80fa24 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -2987,35 +2987,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) From eb8d9190154a72d557674fcffb2a397bce5daed9 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Tue, 13 Jun 2017 17:17:45 +0200 Subject: [PATCH 3/6] [FIX] sql_db: port fix from psycopg/psycopg2#459 NUL characters must not be used in query parameters, as they will be ignored by libpq, being end-of-string characters. Preventing NULs avoids unexpected results from queries. It is only necessary with psycopg2 versions before 2.7, which includes the upstream fix. --- openerp/sql_db.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/openerp/sql_db.py b/openerp/sql_db.py index a3af4be677a..22bed4eabbc 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 From d655824028dd144e449a9ab835f827f6fdfd3260 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Wed, 14 Jun 2017 16:29:55 +0200 Subject: [PATCH 4/6] [FIX] http: force protocol when missing in URL --- openerp/http.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openerp/http.py b/openerp/http.py index e3de9c95665..18b91b94e20 100644 --- a/openerp/http.py +++ b/openerp/http.py @@ -160,6 +160,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 From 090d7a51beb51b7b27a9e7e4f2f7846fca2d3c89 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Thu, 15 Jun 2017 16:17:35 +0200 Subject: [PATCH 5/6] [FIX] auth_oauth: correctly register token field --- addons/auth_oauth/res_users.py | 3 +++ 1 file changed, 3 insertions(+) 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): From 89eca9647636b1d801cf52aa98d83131b99d48fd Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Thu, 15 Jun 2017 16:36:05 +0200 Subject: [PATCH 6/6] [FIX] anonymization: correct pickle protocol and import --- addons/anonymization/anonymization.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/addons/anonymization/anonymization.py b/addons/anonymization/anonymization.py index ac7aa6e438e..b0889066fa4 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 @@ -461,7 +458,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 = {