← Back to team overview

openerp-community-reviewer team mailing list archive

lp:~camptocamp/stock-logistic-flows/add-stock_split_picking-jge into lp:stock-logistic-flows/7.0

 

Joël Grand-Guillaume @ camptocamp has proposed merging lp:~camptocamp/stock-logistic-flows/add-stock_split_picking-jge into lp:stock-logistic-flows/7.0.

Commit message:
[ADD] module stock_split_picking. This addon adds a "Split" button on the out picking from header. It works like classical picking split (when you deliver) but does not pass backorder and backorder lines to state done.

Requested reviews:
  Stock and Logistic Core Editors (stock-logistic-core-editors)

For more details, see:
https://code.launchpad.net/~camptocamp/stock-logistic-flows/add-stock_split_picking-jge/+merge/193605

Hi,


This proposal add the module stock_split_picking. This addon adds a "Split" button on the out picking from header.
It works like classical picking split (when you deliver) but does not pass backorder
and backorder lines to state done.

Regards,


-- 
https://code.launchpad.net/~camptocamp/stock-logistic-flows/add-stock_split_picking-jge/+merge/193605
Your team Stock and Logistic Core Editors is requested to review the proposed merge of lp:~camptocamp/stock-logistic-flows/add-stock_split_picking-jge into lp:stock-logistic-flows/7.0.
=== modified file 'picking_dispatch/picking_dispatch.py'
--- picking_dispatch/picking_dispatch.py	2012-12-10 10:51:23 +0000
+++ picking_dispatch/picking_dispatch.py	2013-11-01 13:48:45 +0000
@@ -23,7 +23,7 @@
 from openerp.osv.orm import Model
 from openerp.osv import osv, fields
 from openerp.osv.osv import except_osv
-from tools.translate import _
+from openerp.tools.translate import _
 
 _logger = logging.getLogger(__name__)
 

=== modified file 'product_serial/__init__.py'
--- product_serial/__init__.py	2013-08-01 12:40:27 +0000
+++ product_serial/__init__.py	2013-11-01 13:48:45 +0000
@@ -1,3 +1,4 @@
+<<<<<<< TREE
 # -*- encoding: utf-8 -*-
 ##############################################################################
 #
@@ -24,3 +25,31 @@
 import company
 import wizard
 
+=======
+# -*- encoding: utf-8 -*-
+##############################################################################
+#
+#    Product serial module for OpenERP
+#    Copyright (C) 2008 Raphaël Valyi
+#
+#    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 <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+import product
+import stock
+import company
+import prodlot_wizard
+
+>>>>>>> MERGE-SOURCE

=== modified file 'product_serial/__openerp__.py'
--- product_serial/__openerp__.py	2013-10-17 11:28:37 +0000
+++ product_serial/__openerp__.py	2013-11-01 13:48:45 +0000
@@ -1,3 +1,4 @@
+<<<<<<< TREE
 # -*- encoding: utf-8 -*-
 ##############################################################################
 #
@@ -63,3 +64,52 @@
     "installable": True
 }
 
+=======
+# -*- encoding: utf-8 -*-
+##############################################################################
+#
+#    Copyright (C) 2008 Raphaël Valyi
+#
+#    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 <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+{
+    "name" : "Unique serial number management",
+    "version" : "1.0.0",
+    "author" : "Akretion, NaN·tic",
+    "website" : "http://www.akretion.com";,
+    "depends" : ['stock'],
+    "category" : "Generic Modules/Inventory Control",
+    "license": "AGPL-3",
+    "description":"""Turns production lot tracking numbers into unique per product instance code (serial number).
+    Moreover, it
+    1) adds a new selection field on the product form to enable or disable this behavior and with split type choice (you should also enable in/out tracking)
+    2) then forbids to perform a move if a move involves more than one product instance
+    3) automagically splits up picking list movements into one movement per product instance or logistical unit packing qty (in that case, only the first LU is taken into account at the present time. Improvement to take them all to be done !!!)
+    4) turns incoming pickings into an editable grid where you can directly type the codes
+    of a new production and tracking number/code to create and associate to the move (it also checks it
+    doesn't exist yet)
+
+    We would also like to extend this module to split automatic production orders (from MRP engine) into several individual production orders in order
+    to make it easy to encode the serial numbers in the production. Let us know if you would like that simple extension to be made.
+    """,
+    "init_xml" : [],
+    "demo_xml" : [],
+    "update_xml" : ["product_view.xml", "company_view.xml", "stock_view.xml"],
+    "active": False,
+    "installable": True
+}
+
+>>>>>>> MERGE-SOURCE

=== modified file 'product_serial/company.py'
--- product_serial/company.py	2013-05-16 06:14:17 +0000
+++ product_serial/company.py	2013-11-01 13:48:45 +0000
@@ -1,3 +1,4 @@
+<<<<<<< TREE
 # -*- encoding: utf-8 -*-
 ##############################################################################
 #
@@ -37,3 +38,44 @@
         'is_group_invoice_line': True,
     }
 
+=======
+# -*- encoding: utf-8 -*-
+##############################################################################
+#
+#    Product serial module for OpenERP
+#    Copyright (C) 2010-2011 Anevia. All Rights Reserved
+#    (written by Sebastien Beau <sebastien.beau@xxxxxxxxxxxx>)
+#
+#    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 <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from openerp.osv import osv, fields
+
+class company(osv.osv):
+    _inherit = 'res.company'
+
+    _columns = {
+        'autosplit_is_active': fields.boolean('Active auto split', help="Active the automatic split of move lines on the pickings."),
+        'is_group_invoice_line': fields.boolean('Group invoice lines', help="If active, OpenERP will group the identical invoice lines. If inactive, each move line will generate one invoice line."),
+    }
+
+    _defaults = {
+        'autosplit_is_active': lambda *a: True,
+        'is_group_invoice_line': lambda *a: True,
+    }
+
+company()
+
+>>>>>>> MERGE-SOURCE

=== modified file 'product_serial/company_view.xml'
--- product_serial/company_view.xml	2013-05-11 16:09:05 +0000
+++ product_serial/company_view.xml	2013-11-01 13:48:45 +0000
@@ -1,3 +1,4 @@
+<<<<<<< TREE
 <?xml version="1.0" encoding="utf-8"?>
 <openerp>
 <data>
@@ -18,3 +19,26 @@
 
 </data>
 </openerp>
+=======
+<?xml version="1.0" ?>
+<openerp>
+    <data>
+
+        <record id="autosplit_company" model="ir.ui.view">
+            <field name="name">res.company.autosplit.config</field>
+            <field name="model">res.company</field>
+            <field name="type">form</field>
+            <field name="inherit_id" ref="base.view_company_form"/>
+          	<field name="arch" type="xml">
+                <page string="Configuration" position="inside">
+                   	<separator string="Product serial" colspan="4"/>
+                   	<field name="autosplit_is_active"/>
+                   	<field name="is_group_invoice_line"/>
+                   	<newline/>
+                </page>
+            </field>
+        </record>
+
+    </data>
+</openerp>
+>>>>>>> MERGE-SOURCE

=== added file 'product_serial/i18n/fr_BE.po'
--- product_serial/i18n/fr_BE.po	1970-01-01 00:00:00 +0000
+++ product_serial/i18n/fr_BE.po	2013-11-01 13:48:45 +0000
@@ -0,0 +1,75 @@
+# Translation of OpenERP Server.
+# This file contains the translation of the following modules:
+#	* mrp_prodlot_autosplit
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: OpenERP Server 5.0.6\n"
+"Report-Msgid-Bugs-To: support@xxxxxxxxxxx\n"
+"POT-Creation-Date: 2009-11-26 10:28:43+0000\n"
+"PO-Revision-Date: 2009-11-26 10:28:43+0000\n"
+"Last-Translator: <>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: mrp_prodlot_autosplit
+#: field:product.product,unique_production_number:0
+msgid "Unique Production Number"
+msgstr ""
+
+#. module: mrp_prodlot_autosplit
+#: constraint:ir.ui.view:0
+msgid "Invalid XML for View Architecture!"
+msgstr ""
+
+#. module: mrp_prodlot_autosplit
+#: model:ir.module.module,description:mrp_prodlot_autosplit.module_meta_information
+msgid "Turns production lot tracking numbers into unique per product instance code (serial number).\n"
+"    Moreover, it\n"
+"    1) adds a new checkbox on the product form to enable or disable this behavior (you should also enable in/out tracking)\n"
+"    2) then forbids to perform a move if a move involves more than one product instance\n"
+"    3) automagically splits up picking list movements into one movement per product instance\n"
+"    4) turns incoming pickings into an editable grid where you can directly type the code\n"
+"    of a new production number/code to create and associate to the move (it also checks it\n"
+"    doesn't exist yet)\n"
+"    \n"
+"    Important Note 1: serial numbers are more easily encode using an editable tree grid, including a special field with new serial to be created.\n"
+"    However, there is currently a limitation in the OpenObject framework preventing from easily changing non editable trees to editable trees\n"
+"    by simple extension. Rather than overwriting all views, we prefer give only one example: the active customied view for easy serial encoding\n"
+"    is available using Stock Management > Incoming Products. Looking  that that view definition, the same thing is easily achieved in\n"
+"    other picking list, like out going products for instance. However it's not \"on\" by default, you would need to work it out for your case.\n"
+"    Meanwhile, we hope Tiny add a third \"merge_attributes\" view extension point to the 3 existing ones: \"before\", \"after\" and \"replace\".\n"
+"    It would basically simply merge the attributes given (redefined) in the original view XML and let inner content unchanged.\n"
+"    Blueprint is registred here: https://blueprints.launchpad.net/openobject-server/+spec/merge-attributes-view-extension-point\n";
+"    \n"
+"    Important Note 2: this module doesn't split product bill of materials in MRP since they don't use pickings\n"
+"    A good workaround when generating production orders manually one by one is too define several lines of individual products in nomemclatures\n"
+"    and produce 1 by 1 (if possible) to make it easier to encode unique prodlot in production orders too.\n"
+"    We would also like to extend this module to split automatic production orders (from MRP engine) into several individual production orders in order\n"
+"    to make it easy to encode the serial numbers in the production. Let us know if you would like that simple extension to be made.\n"
+"    "
+msgstr ""
+
+#. module: mrp_prodlot_autosplit
+#: field:stock.production.lot,last_location_id:0
+msgid "Last Location"
+msgstr ""
+
+#. module: mrp_prodlot_autosplit
+#: model:ir.module.module,shortdesc:mrp_prodlot_autosplit.module_meta_information
+msgid "Unique serial number management"
+msgstr ""
+
+#. module: mrp_prodlot_autosplit
+#: field:stock.move,new_prodlot_code:0
+msgid "Production Tracking Code To Create"
+msgstr ""
+
+#. module: mrp_prodlot_autosplit
+#: view:stock.picking:0
+msgid "Stock Moves"
+msgstr ""
+

=== renamed file 'product_serial/wizard/prodlot_wizard.py' => 'product_serial/prodlot_wizard.py'
--- product_serial/wizard/prodlot_wizard.py	2013-08-01 12:40:27 +0000
+++ product_serial/prodlot_wizard.py	2013-11-01 13:48:45 +0000
@@ -1,3 +1,4 @@
+<<<<<<< TREE
 # -*- encoding: utf-8 -*-
 ##############################################################################
 #
@@ -116,3 +117,124 @@
 
         return True
 
+=======
+# -*- encoding: utf-8 -*-
+##############################################################################
+#
+#    Product serial module for OpenERP
+#    Copyright (C) 2010 NaN Projectes de Programari Lliure, S.L.
+#                       http://www.NaN-tic.com
+#
+#    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 <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from openerp.osv import osv, fields
+from openerp.tools.translate import _
+
+def is_integer(value):
+    try:
+        int(value)
+        return True
+    except:
+        return False
+
+class stock_picking_prodlot_selection_wizard(osv.osv_memory):
+    _name = 'stock.picking.prodlot.selection'
+
+    _columns = {
+        'product_id': fields.many2one('product.product', 'Product', required=True),
+        'first_lot': fields.char('First Lot Number', size=256),
+        'last_lot': fields.char('Last Lot Number', size=256),
+    }
+
+    def action_cancel(self, cr, uid, ids, context=None):
+        return {}
+
+    def action_accept(self, cr, uid, ids, context=None):
+        if context is None:
+            context = {}
+        if not ids:
+            return {}
+        if not 'active_id' in context:
+            return {}
+
+        record = self.browse(cr, uid, ids[0], context)
+        first = record.first_lot
+        last = record.last_lot
+        if len(first) != len(last):
+            raise osv.except_osv(_('Invalid lot numbers'), _('First and last lot numbers must have the same length.'))
+
+
+        first_number = ''
+        last_number = ''
+        position = -1
+        for x in xrange(len(first)):
+            if not position and first[x] == last[x]:
+                continue
+            if not position:
+                position = x
+            if not is_integer(first[x]) or not is_integer(last[x]):
+                raise osv.except_osv(_('Invalid lot numbers'), _('First and last lot numbers differ in non-numeric values.'))
+            first_number += first[x]
+            last_number += last[x]
+
+        if position >= 0:
+            prefix = first[:position-1]
+        else:
+            prefix = ''
+        
+        number_fill = len(first_number)
+        first_number = int(first_number)
+        last_number = int(last_number)
+
+        if last_number < first_number:
+            raise osv.except_osv(_('Invalid lot numbers'), _('First lot number is greater than the last one.'))
+
+        picking_id = context['active_id']
+        current_number = first_number
+        for move in self.pool.get('stock.picking').browse(cr, uid, picking_id, context).move_lines:
+            if move.prodlot_id or move.product_id != record.product_id:
+                continue
+
+            current_lot = '%%s%%0%dd' % number_fill % (prefix, current_number)
+            lot_ids = self.pool.get('stock.production.lot').search(cr, uid, [('name','=',current_lot)], limit=1, context=context)
+            if not lot_ids:
+                raise osv.except_osv(_('Invalid lot numbers'), _('Production lot %s not found.') % current_lot)
+
+            ctx = context.copy()
+            ctx['location_id'] = move.location_id.id
+            prodlot = self.pool.get('stock.production.lot').browse(cr, uid, lot_ids[0], ctx)
+            
+            if prodlot.product_id != record.product_id:
+                raise osv.except_osv(_('Invalid lot numbers'), _('Production lot %s exists but not for product %s.') % (current_lot, record.product_id.name))
+
+            if prodlot.stock_available < move.product_qty:
+                raise osv.except_osv(_('Invalid lot numbers'), _('Not enough stock available of production lot %s.') % current_lot)
+            
+            self.pool.get('stock.move').write(cr, uid, [move.id], {
+                'prodlot_id': lot_ids[0],
+            }, context)
+
+            current_number += 1
+            if current_number > last_number:
+                break
+
+        return {}
+
+        
+
+stock_picking_prodlot_selection_wizard()
+
+>>>>>>> MERGE-SOURCE

=== modified file 'product_serial/product.py'
--- product_serial/product.py	2013-06-08 12:33:15 +0000
+++ product_serial/product.py	2013-11-01 13:48:45 +0000
@@ -1,3 +1,4 @@
+<<<<<<< TREE
 # -*- encoding: utf-8 -*-
 ##############################################################################
 #
@@ -39,3 +40,42 @@
         'lot_split_type': 'none',
     }
 
+=======
+# -*- encoding: utf-8 -*-
+##############################################################################
+#
+#    Product serial module for OpenERP
+#    Copyright (C) 2008 Raphaël Valyi
+#
+#    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 <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from openerp.osv import fields, osv
+
+class product_product(osv.osv):
+    _inherit = "product.product"
+
+    _columns = {
+        'lot_split_type': fields.selection([('none','None'),
+                                    ('single','Single'),
+                                    ('lu','Logistical Unit')], 'Lot split type', required=True, help="None: no split ; single: 1 line/product unit ; Logistical Unit: split using the 1st Logistical Unit quantity of the product form packaging tab (to be improved to take into account all LU)"),
+    }
+    _defaults = {
+        'lot_split_type': lambda *a: 'none',
+    }
+    
+product_product()
+
+>>>>>>> MERGE-SOURCE

=== modified file 'product_serial/product_view.xml'
--- product_serial/product_view.xml	2013-06-08 12:33:15 +0000
+++ product_serial/product_view.xml	2013-11-01 13:48:45 +0000
@@ -1,3 +1,4 @@
+<<<<<<< TREE
 <?xml version="1.0" encoding="utf-8"?>
 <openerp>
 <data>
@@ -18,3 +19,23 @@
 
 </data>
 </openerp>
+=======
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+    <data>
+
+        <record id="view_normal_unique_production_number_form" model="ir.ui.view">
+            <field name="name">product.normal.stock.form.unique_production_number.inherit</field>
+            <field name="model">product.product</field>
+            <field name="type">form</field>
+            <field name="inherit_id" ref="stock.view_normal_stock_property_form"/>
+            <field name="arch" type="xml">
+                <field name="track_outgoing" position="after">
+                		<field name="lot_split_type" />
+                </field>
+            </field>
+        </record>
+        
+     </data>
+</openerp>
+>>>>>>> MERGE-SOURCE

=== modified file 'product_serial/stock.py'
--- product_serial/stock.py	2013-06-08 12:33:15 +0000
+++ product_serial/stock.py	2013-11-01 13:48:45 +0000
@@ -1,3 +1,4 @@
+<<<<<<< TREE
 # -*- encoding: utf-8 -*-
 ##############################################################################
 #
@@ -303,3 +304,302 @@
                                     help="Display the current stock location of this production lot"),
     }
 
+=======
+# -*- encoding: utf-8 -*-
+##############################################################################
+#
+#    Product serial module for OpenERP
+#    Copyright (C) 2008 Raphaël Valyi
+#    Copyright (C) 2011 Anevia S.A. - Ability to group invoice lines
+#              written by Alexis Demeaulte <alexis.demeaulte@xxxxxxxxxx>
+#    Copyright (C) 2011 Akretion - Ability to split lines on logistical units
+#              written by Emmanuel Samyn
+#
+#    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 <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from openerp.osv import fields, osv
+from openerp.tools.translate import _
+import hashlib
+
+
+class stock_move(osv.osv):
+    _inherit = "stock.move"
+    # We order by product name because otherwise, after the split,
+    # the products are "mixed" and not grouped by product name any more
+    _order = "picking_id, name, id"
+
+    def copy(self, cr, uid, id, default=None, context=None):
+        if not default:
+            default = {}
+        default['new_prodlot_code'] = False
+        return super(stock_move, self).copy(cr, uid, id, default, context=context)
+
+    def _get_prodlot_code(self, cr, uid, ids, field_name, arg, context=None):
+        res = {}
+        for move in self.browse(cr, uid, ids):
+            res[move.id] = move.prodlot_id and move.prodlot_id.name or False
+        return res
+
+    def _set_prodlot_code(self, cr, uid, ids, name, value, arg, context=None):
+        if not value: return False
+
+        if isinstance(ids, (int, long)):
+            ids = [ids]
+
+        for move in self.browse(cr, uid, ids, context=context):
+            product_id = move.product_id.id
+            existing_prodlot = move.prodlot_id
+            if existing_prodlot: #avoid creating a prodlot twice
+                self.pool.get('stock.production.lot').write(cr, uid, existing_prodlot.id, {'name': value})
+            else:
+                prodlot_id = self.pool.get('stock.production.lot').create(cr, uid, {
+                    'name': value,
+                    'product_id': product_id,
+                })
+                move.write({'prodlot_id' : prodlot_id})
+
+    def _get_tracking_code(self, cr, uid, ids, field_name, arg, context=None):
+        res = {}
+        for move in self.browse(cr, uid, ids):
+            res[move.id] = move.tracking_id and move.tracking_id.name or False
+        return res
+
+    def _set_tracking_code(self, cr, uid, ids, name, value, arg, context=None):
+        if not value: return False
+
+        if isinstance(ids, (int, long)):
+            ids = [ids]
+
+        for move in self.browse(cr, uid, ids, context=context):
+            product_id = move.product_id.id
+            existing_tracking = move.tracking_id
+            if existing_tracking: #avoid creating a tracking twice
+                self.pool.get('stock.tracking').write(cr, uid, existing_tracking.id, {'name': value})
+            else:
+                tracking_id = self.pool.get('stock.tracking').create(cr, uid, {
+                    'name': value,
+                })
+                move.write({'tracking_id' : tracking_id})
+
+    _columns = {
+        'new_prodlot_code': fields.function(_get_prodlot_code, fnct_inv=_set_prodlot_code,
+                                            method=True, type='char', size=64,
+                                            string='Prodlot fast input', select=1
+                                           ),
+        'new_tracking_code': fields.function(_get_tracking_code, fnct_inv=_set_tracking_code,
+                                            method=True, type='char', size=64,
+                                            string='Tracking fast input', select=1
+                                           ),
+    }
+
+    def action_done(self, cr, uid, ids, context=None):
+        """
+        If we autosplit moves without reconnecting them 1 by 1, at least when some move which has descendants is split
+        The following situation would happen (alphabetical order is order of creation, initially b and a pre-exists, then a is split, so a might get assigned and then split too):
+        Incoming moves b, c, d
+        Outgoing moves a, e, f
+        Then we have those links: b->a, c->a, d->a
+        and: b->, b->e, b->f
+        The following code will detect this situation and reconnect properly the moves into only: b->a, c->e and d->f
+        """
+        result = super(stock_move, self).action_done(cr, uid, ids, context)
+        for move in self.browse(cr, uid, ids):
+            if move.product_id.lot_split_type and move.move_dest_id and move.move_dest_id.id:
+                cr.execute("select stock_move.id from stock_move_history_ids left join stock_move on stock_move.id = stock_move_history_ids.child_id where parent_id=%s and stock_move.product_qty=1", (move.id,))
+                unitary_out_moves = cr.fetchall()
+                if unitary_out_moves and len(unitary_out_moves) > 1:
+                    unitary_in_moves = []
+                    out_node = False
+                    counter = 0
+                    while len(unitary_in_moves) != len(unitary_out_moves) and counter < len(unitary_out_moves):
+                        out_node = unitary_out_moves[counter][0]
+                        cr.execute("select stock_move.id from stock_move_history_ids left join stock_move on stock_move.id = stock_move_history_ids.parent_id where child_id=%s and stock_move.product_qty=1", (out_node,))
+                        unitary_in_moves = cr.fetchall()
+                        counter += 1
+
+                    if len(unitary_in_moves) == len(unitary_out_moves):
+                        unitary_out_moves.reverse()
+                        unitary_out_moves.pop()
+                        unitary_in_moves.reverse()
+                        unitary_in_moves.pop()
+                        counter = 0
+                        for unitary_in_move in unitary_in_moves:
+                            cr.execute("delete from stock_move_history_ids where parent_id=%s and child_id=%s", (unitary_in_moves[counter][0], out_node))
+                            cr.execute("update stock_move_history_ids set parent_id=%s where parent_id=%s and child_id=%s", (unitary_in_moves[counter][0], move.id, unitary_out_moves[counter][0]))
+                            counter += 1
+
+        return result
+
+    def split_move(self, cr, uid, ids, context=None):
+        all_ids = list(ids)
+        for move in self.browse(cr, uid, ids, context=context):
+            qty = move.product_qty
+            lu_qty = False
+            if move.product_id.lot_split_type == 'lu':
+                if not move.product_id.packaging:
+                    raise osv.except_osv(_('Error :'), _("Product '%s' has 'Lot split type' = 'Logistical Unit' but is missing packaging information.") % (move.product_id.name))
+                lu_qty = move.product_id.packaging[0].qty
+            elif move.product_id.lot_split_type == 'single':
+                lu_qty = 1
+            if lu_qty and qty > 1:
+                # Set existing move to LU quantity
+                self.write(cr, uid, move.id, {'product_qty': lu_qty, 'product_uos_qty': move.product_id.uos_coeff})
+                qty -= lu_qty
+                # While still enough qty to create a new move, create it
+                while qty >= lu_qty:
+                    all_ids.append( self.copy(cr, uid, move.id, {'state': move.state, 'prodlot_id': None}) )
+                    qty -= lu_qty
+                # Create a last move for the remainder qty
+                if qty > 0:
+                    all_ids.append( self.copy(cr, uid, move.id, {'state': move.state, 'prodlot_id': None, 'product_qty': qty}) )
+        return all_ids
+
+stock_move()
+
+
+class stock_picking(osv.osv):
+    _inherit = "stock.picking"
+
+    def action_assign_wkf(self, cr, uid, ids):
+        result = super(stock_picking, self).action_assign_wkf(cr, uid, ids)
+
+        for picking in self.browse(cr, uid, ids):
+            if picking.company_id.autosplit_is_active:
+                for move in picking.move_lines:
+                    # Auto split
+                    if ((move.product_id.track_production and move.location_id.usage == 'production') or \
+                        (move.product_id.track_production and move.location_dest_id.usage == 'production') or \
+                        (move.product_id.track_incoming and move.location_id.usage == 'supplier') or \
+                        (move.product_id.track_outgoing and move.location_dest_id.usage == 'customer')):
+                        self.pool.get('stock.move').split_move(cr, uid, [move.id])
+
+        return result
+
+    # Because stock move line can be splitted by the module, we merge
+    # invoice lines (if option 'is_group_invoice_line' is activated for the company)
+    # at the following conditions :
+    #   - the product is the same and
+    #   - the discount is the same and
+    #   - the unit price is the same and
+    #   - the description is the same and
+    #   - taxes are the same
+    #   - they are from the same sale order lines (requires extra-code)
+    # we merge invoice line together and do the sum of quantity and
+    # subtotal.
+    def action_invoice_create(self, cursor, user, ids, journal_id=False,
+        group=False, type='out_invoice', context=None):
+        invoice_dict = super(stock_picking, self).action_invoice_create(cursor, user,
+            ids, journal_id, group, type, context=context)
+
+        for picking_key in invoice_dict:
+            invoice = self.pool.get('account.invoice').browse(cursor, user, invoice_dict[picking_key], context=context)
+            if not invoice.company_id.is_group_invoice_line:
+                continue
+
+            new_line_list = {}
+
+            for line in invoice.invoice_line:
+
+                # Build a key
+                key = unicode(line.product_id.id) + ";" \
+                    + unicode(line.discount) + ";" \
+                    + unicode(line.price_unit) + ";" \
+                    + line.name + ";"
+
+                # Add the tax key part
+                tax_tab = []
+                for tax in line.invoice_line_tax_id:
+                    tax_tab.append(tax.id)
+                tax_tab.sort()
+                for tax in tax_tab:
+                    key = key + unicode(tax) + ";"
+
+                # Add the sale order line part but check if the field exist because
+                # it's install by a specific module (not from addons)
+                if self.pool.get('ir.model.fields').search(cursor, user,
+                        [('name', '=', 'sale_order_lines'), ('model', '=', 'account.invoice.line')], context=context) != []:
+                    order_line_tab = []
+                    for order_line in line.sale_order_lines:
+                        order_line_tab.append(order_line.id)
+                    order_line_tab.sort()
+                    for order_line in order_line_tab:
+                        key = key + unicode(order_line) + ";"
+
+
+                # Get the hash of the key
+                hash_key = hashlib.sha224(key.encode('utf8')).hexdigest()
+
+                # if the key doesn't already exist, we keep the invoice line
+                # and we add the key to new_line_list
+                if not new_line_list.has_key(hash_key):
+                    new_line_list[hash_key] = {
+                        'id': line.id,
+                        'quantity': line.quantity,
+                        'price_subtotal': line.price_subtotal,
+                    }
+                # if the key already exist, we update new_line_list and 
+                # we delete the invoice line
+                else:
+                    new_line_list[hash_key]['quantity'] = new_line_list[hash_key]['quantity'] + line.quantity
+                    new_line_list[hash_key]['price_subtotal'] = new_line_list[hash_key]['price_subtotal'] \
+                                                            +  line.price_subtotal
+                    self.pool.get('account.invoice.line').unlink(cursor, user, line.id, context=context)
+
+            # Write modifications made on invoice lines
+            for hash_key in new_line_list:
+                line_id = new_line_list[hash_key]['id']
+                del new_line_list[hash_key]['id']
+                self.pool.get('account.invoice.line').write(cursor, user, line_id, new_line_list[hash_key], context=context)
+
+        return invoice_dict
+
+stock_picking()
+
+
+class stock_production_lot(osv.osv):
+    _inherit = "stock.production.lot"
+
+    def _last_location_id(self, cr, uid, ids, field_name, arg, context=None):
+        """Retrieves the last location where the product with given serial is.
+        Instead of using dates we assume the product is in the location having the
+        highest number of products with the given serial (should be 1 if no mistake). This
+        is better than using move dates because moves can easily be encoded at with wrong dates."""
+        res = {}
+
+        for prodlot_id in ids:
+            cr.execute(
+                "select location_dest_id " \
+                "from stock_move inner join stock_report_prodlots on stock_report_prodlots.location_id = location_dest_id and stock_report_prodlots.prodlot_id = %s " \
+                "where stock_move.prodlot_id = %s and stock_move.state=%s "\
+                "order by stock_report_prodlots.qty DESC ",
+                (prodlot_id, prodlot_id, 'done'))
+            results = cr.fetchone()
+
+            #TODO return tuple to avoid name_get being requested by the GTK client
+            res[prodlot_id] = results and results[0] or False
+
+        return res
+
+    _columns = {
+        'last_location_id': fields.function(_last_location_id, method=True,
+                                            type="many2one", relation="stock.location",
+                                            string="Last location",
+                                            help="Display the current stock location of this production lot"),
+    }
+
+stock_production_lot()
+
+>>>>>>> MERGE-SOURCE

=== modified file 'product_serial/stock_view.xml'
--- product_serial/stock_view.xml	2013-05-20 19:39:45 +0000
+++ product_serial/stock_view.xml	2013-11-01 13:48:45 +0000
@@ -1,3 +1,4 @@
+<<<<<<< TREE
 <?xml version="1.0" encoding="utf-8"?>
 <openerp>
 <data>
@@ -155,3 +156,304 @@
 
 </data>
 </openerp>
+=======
+<?xml version="1.0"?>
+<openerp>
+<data>
+
+    <!-- In the form view of Incoming/Outgoing products, make the tree view of move lines editable and set the parameters that are only present in the form view to the fields of the tree view-->
+    <record id="view_picking_in_form_editable_move_lines" model="ir.ui.view">
+        <field name="name">view.picking.in.form.editable_move_lines</field>
+        <field name="model">stock.picking</field>
+        <field name="inherit_id" ref="stock.view_picking_in_form" />
+        <field name="type">form</field>
+        <field name="arch" type="xml">
+            <xpath expr="/form/notebook/page/field/tree[@string='Stock Moves']" position="attributes">
+                <attribute name="editable">bottom</attribute>
+            </xpath>
+            <xpath expr="/form/notebook/page/field/tree/field[@name='product_id']" position="attributes">
+                <attribute name="on_change">onchange_product_id(product_id,location_id,location_dest_id, parent.address_id)</attribute>
+            </xpath>
+            <!-- we need to have the "name" field in the tree view, because it is a required field that is set by the on_change on product_id... otherwise we get an error message when adding a new move line via the editable tree view -->
+            <xpath expr="/form/notebook/page/field/tree/field[@name='product_id']" position="after">
+                <field name="name" invisible="1"/>
+            </xpath>
+            <xpath expr="/form/notebook/page/field/tree/field[@name='product_qty']" position="attributes">
+                <attribute name="on_change">onchange_quantity(product_id, product_qty, product_uom, product_uos)</attribute>
+            </xpath>
+            <xpath expr="/form/notebook/page/field/tree/field[@name='prodlot_id']" position="attributes">
+                <attribute name="context">{'location_id':location_id, 'product_id':product_id}</attribute>
+                <attribute name="domain">[('product_id','=?',product_id)]</attribute>
+                <attribute name="on_change">onchange_lot_id(prodlot_id, product_qty, location_id, product_id, product_uom)</attribute>
+            </xpath>
+            <xpath expr="/form/notebook/page/field/tree/field[@name='location_dest_id']" position="attributes">
+                <attribute name="domain">[('usage','=','internal')]</attribute>
+            </xpath>
+        </field>
+    </record>
+
+    <record id="view_picking_out_form_editable_move_lines" model="ir.ui.view">
+        <field name="name">view.picking.out.form.editable_move_lines</field>
+        <field name="model">stock.picking</field>
+        <field name="inherit_id" ref="stock.view_picking_out_form" />
+        <field name="type">form</field>
+        <field name="arch" type="xml">
+            <xpath expr="/form/notebook/page/field/tree[@string='Stock Moves']" position="attributes">
+                <attribute name="editable">bottom</attribute>
+            </xpath>
+            <xpath expr="/form/notebook/page/field/tree/field[@name='product_id']" position="attributes">
+                <attribute name="on_change">onchange_product_id(product_id,location_id,location_dest_id, parent.address_id)</attribute>
+            </xpath>
+            <!-- we need to have the "name" field in the tree view, because it is a required field that is set by the on_change on product_id... otherwise we get an error message when adding a new move line via the editable tree view -->
+            <xpath expr="/form/notebook/page/field/tree/field[@name='product_id']" position="after">
+                <field name="name" invisible="1"/>
+            </xpath>
+            <xpath expr="/form/notebook/page/field/tree/field[@name='product_qty']" position="attributes">
+                <attribute name="on_change">onchange_quantity(product_id, product_qty, product_uom, product_uos)</attribute>
+            </xpath>
+            <xpath expr="/form/notebook/page/field/tree/field[@name='product_uos']" position="attributes">
+                <attribute name="on_change">onchange_quantity(product_id, product_qty, product_uom, product_uos)</attribute>
+            </xpath>
+            <xpath expr="/form/notebook/page/field/tree/field[@name='prodlot_id']" position="attributes">
+                <attribute name="context">{'location_id':location_id, 'product_id':product_id}</attribute>
+                <attribute name="domain">[('product_id','=?',product_id)]</attribute>
+                <attribute name="on_change">onchange_lot_id(prodlot_id, product_qty, location_id, product_id, product_uom)</attribute>
+            </xpath>
+            <xpath expr="/form/notebook/page/field/tree/field[@name='location_id']" position="attributes">
+                <attribute name="domain">[('usage','=','internal')]</attribute>
+            </xpath>
+        </field>
+    </record>
+
+    <!-- In the form view of Incoming products, add the "new_prodlot_code" fields -->
+    <record id="view_picking_in_form_new_prodlot_code" model="ir.ui.view">
+        <field name="name">view.picking.in.form.new_prodlot_code</field>
+        <field name="model">stock.picking</field>
+        <field name="inherit_id" ref="stock.view_picking_in_form" />
+        <field name="type">form</field>
+        <field name="arch" type="xml">
+            <xpath expr="/form/notebook/page/field/tree/field[@name='prodlot_id']" position="before">
+                <field name="new_prodlot_code" />
+            </xpath>
+        </field>
+    </record>
+
+    <!-- In the form view of Incoming products, add the "new_tracking_code" fields -->
+    <record id="view_picking_in_form_new_tracking_code" model="ir.ui.view">
+        <field name="name">view.picking.in.form.new_tracking_code</field>
+        <field name="model">stock.picking</field>
+        <field name="inherit_id" ref="stock.view_picking_in_form" />
+        <field name="type">form</field>
+        <field name="arch" type="xml">
+            <xpath expr="/form/notebook/page/field/tree/field[@name='tracking_id']" position="before">
+                <field name="new_tracking_code" />
+            </xpath>
+        </field>
+    </record>
+
+    <!-- We should have both source stock + dest location in the form view of Incoming/Outgoing products/Internal moves -->
+    <record id="view_picking_in_form_source_stock_location" model="ir.ui.view">
+        <field name="name">view.picking.in.form.source_stock_location</field>
+        <field name="model">stock.picking</field>
+        <field name="inherit_id" ref="stock.view_picking_in_form" />
+        <field name="type">form</field>
+        <field name="arch" type="xml">
+            <xpath expr="/form/notebook/page/field/tree/field[@name='location_dest_id']" position="before">
+                <field name="location_id" domain="[('usage','&lt;&gt;','view')]" />
+            </xpath>
+        </field>
+    </record>
+
+    <record id="view_picking_out_form_dest_stock_location" model="ir.ui.view">
+        <field name="name">view.picking.out.form.dest_stock_location</field>
+        <field name="model">stock.picking</field>
+        <field name="inherit_id" ref="stock.view_picking_out_form" />
+        <field name="type">form</field>
+        <field name="arch" type="xml">
+            <xpath expr="/form/notebook/page/field/tree/field[@name='location_id']" position="after">
+                <field name="location_dest_id" domain="[('usage','&lt;&gt;','view')]" />
+            </xpath>
+        </field>
+    </record>
+
+    <record id="view_picking_form_source_stock_location" model="ir.ui.view">
+        <field name="name">view.picking.form.source_stock_location</field>
+        <field name="model">stock.picking</field>
+        <field name="inherit_id" ref="stock.view_picking_form" />
+        <field name="type">form</field>
+        <field name="arch" type="xml">
+            <xpath expr="/form/notebook/page/field/tree/field[@name='location_dest_id']" position="before">
+                <field name="location_id" domain="[('usage','&lt;&gt;','view')]" />
+            </xpath>
+        </field>
+    </record>
+
+
+    <record id="view_picking_in_form_manual_split" model="ir.ui.view">
+        <field name="name">view.picking.in.form.manual_split</field>
+        <field name="model">stock.picking</field>
+        <field name="inherit_id" ref="stock.view_picking_in_form" />
+        <field name="type">form</field>
+        <field name="priority">24</field> <!-- inherit after product_hardware_revision -->
+        <field name="arch" type="xml">
+            <xpath expr="/form/notebook/page/field/form/group/button[@string='Split']" position="after">
+                <label string="" colspan="3" />
+                <button name="split_move" string="Manual split"
+                    groups="base.group_extended"
+                    states="draft,waiting,confirmed,assigned"
+                    type="object" icon="terp-stock_effects-object-colorize"
+                    colspan="1" />
+            </xpath>
+            <xpath expr="/form/notebook/page/field/tree/button[@string='Split in production lots']" position="replace">
+                <button name="split_move" string="Manual split"
+                    groups="base.group_extended"
+                    states="draft,waiting,confirmed,assigned"
+                    type="object" icon="terp-stock_effects-object-colorize"/>
+            </xpath>
+        </field>
+    </record>
+
+    <record id="view_picking_out_form_manual_split" model="ir.ui.view">
+        <field name="name">view.picking.out.form.manual_split</field>
+        <field name="model">stock.picking</field>
+        <field name="inherit_id" ref="stock.view_picking_out_form" />
+        <field name="type">form</field>
+        <field name="priority">24</field> <!-- inherit after product_hardware_revision -->
+        <field name="arch" type="xml">
+            <xpath expr="/form/notebook/page/field/form/group/button[@string='Split']" position="after">
+                <label string="" colspan="3" />
+                <button name="split_move" string="Manual split"
+                    groups="base.group_extended"
+                    states="draft,waiting,confirmed,assigned"
+                    type="object" icon="terp-stock_effects-object-colorize"
+                    colspan="1" />
+            </xpath>
+            <xpath expr="/form/notebook/page/field/tree/button[@string='Split in production lots']" position="replace">
+                <button name="split_move" string="Manual split"
+                    groups="base.group_extended"
+                    states="draft,waiting,confirmed,assigned"
+                    type="object" icon="terp-stock_effects-object-colorize"/>
+            </xpath>
+        </field>
+    </record>
+
+
+    <!-- Internal moves -->
+    <record id="view_picking_form_manual_split" model="ir.ui.view">
+        <field name="name">view.picking.form.manual_split</field>
+        <field name="model">stock.picking</field>
+        <field name="inherit_id" ref="stock.view_picking_form" />
+        <field name="type">form</field>
+        <field name="arch" type="xml">
+            <xpath expr="/form/notebook/page/field/form/group/button[@string='Split']" position="after">
+                <label string="" colspan="3" />
+                <button name="split_move" string="Manual split"
+                    groups="base.group_extended"
+                    states="draft,waiting,confirmed,assigned"
+                    type="object" icon="terp-stock_effects-object-colorize"
+                    colspan="1" />
+            </xpath>
+            <xpath expr="/form/notebook/page/field/tree/button[@string='Split in production lots']" position="replace">
+                <button name="split_move" string="Manual split"
+                    groups="base.group_extended"
+                    states="draft,waiting,confirmed,assigned"
+                    type="object" icon="terp-stock_effects-object-colorize"/>
+            </xpath>
+        </field>
+    </record>
+
+
+    <!-- Add "last_location_id" on prod lot view -->
+     <record id="view_production_lot_form_unique_production_number" model="ir.ui.view">
+         <field name="name">view_production_lot_form_unique_production_number</field>
+         <field name="model">stock.production.lot</field>
+         <field name="inherit_id" ref="stock.view_production_lot_form"/>
+         <field name="type">form</field>
+         <field name="arch" type="xml">
+             <field name="stock_available" position="after">
+                 <field name="last_location_id"/>
+             </field>
+         </field>
+     </record>
+
+	<!-- Wizard to help users input production lots in batch -->
+<!-- TODO Nan-TIc : port to v6
+	<record id="view_stock_picking_prodlot_selection" model="ir.ui.view">
+		<field name="name">stock.picking.prodlot.selection</field>
+		<field name="model">stock.picking.prodlot.selection</field>
+		<field name="type">form</field>
+		<field name="arch" type="xml">
+			<form string="Select Production Lots">
+				<field name="product_id" colspan="4"/>
+				<field name="first_lot"/>
+				<field name="last_lot"/>
+				<button type="object" name="action_cancel" string="Cancel" icon="gtk-cancel" special="cancel" colspan="2"/>
+				<button type="object" name="action_accept" string="Accept" icon="gtk-ok" colspan="2"/>
+			</form>
+		</field>
+	</record>
+
+	<record model="ir.actions.act_window" id="action_prodlot_selection">
+		<field name="name">Select Production Lots</field>
+		<field name="res_model">stock.picking.prodlot.selection</field>
+		<field name="view_type">form</field>
+		<field name="view_mode">form</field>
+		<field name="target">new</field>
+	</record>
+
+	<record id="view_picking_form" model="ir.ui.view">
+		<field name="name">stock.picking.form.prodlot.selection</field>
+		<field name="model">stock.picking</field>
+		<field name="type">form</field>
+		<field name="inherit_id" ref="stock.view_picking_form"/>
+		<field name="arch" type="xml">
+			<xpath expr="/form/notebook/page/group/label[@colspan='6']" position="replace">
+				<label colspan="5"/>
+				<button type="action" name="%(action_prodlot_selection)d" string="Spread Production Lots" states="draft,confirmed,assigned"/>
+			</xpath>
+		</field>
+	</record>
+
+	<record id="view_picking_in_form" model="ir.ui.view">
+		<field name="name">stock.picking.in.form.prodlot.selection</field>
+		<field name="model">stock.picking</field>
+		<field name="type">form</field>
+		<field name="inherit_id" ref="stock.view_picking_in_form"/>
+		<field name="arch" type="xml">
+			<xpath expr="/form/notebook/page/group/label[@colspan='5']" position="replace">
+				<label colspan="4"/>
+				<button type="action" name="%(action_prodlot_selection)d" string="Spread Production Lots" states="draft,confirmed,assigned"/>
+			</xpath>
+		</field>
+	</record>
+
+	<record id="view_picking_out_form" model="ir.ui.view">
+		<field name="name">stock.picking.out.form</field>
+		<field name="model">stock.picking</field>
+		<field name="type">form</field>
+		<field name="inherit_id" ref="stock.view_picking_out_form"/>
+		<field name="arch" type="xml">
+			<xpath expr="/form/notebook/page/group/label[@colspan='6']" position="replace">
+				<label colspan="5"/>
+				<button type="action" name="%(action_prodlot_selection)d" string="Spread Production Lots" states="draft,confirmed,assigned"/>
+			</xpath>
+		</field>
+	</record>
+
+	<record id="view_picking_delivery_form" model="ir.ui.view">
+		<field name="name">stock.picking.delivery.form</field>
+		<field name="model">stock.picking</field>
+		<field name="type">form</field>
+		<field name="inherit_id" ref="stock.view_picking_delivery_form"/>
+		<field name="arch" type="xml">
+			<xpath expr="/form/notebook/page/group/label[@colspan='6']" position="replace">
+				<label colspan="5"/>
+				<button type="action" name="%(action_prodlot_selection)d" string="Spread Production Lots" states="draft,confirmed,assigned"/>
+			</xpath>
+		</field>
+	</record>
+-->
+
+</data>
+</openerp>
+>>>>>>> MERGE-SOURCE

=== modified file 'stock_move_on_hold/invoice.py'
--- stock_move_on_hold/invoice.py	2012-03-07 12:56:37 +0000
+++ stock_move_on_hold/invoice.py	2013-11-01 13:48:45 +0000
@@ -19,8 +19,8 @@
 #
 #################################################################################
 
-from osv import fields, osv
-from tools.translate import _
+from openerp.osv import fields, osv
+from openerp.tools.translate import _
 
 class account_invoice(osv.osv):
     

=== modified file 'stock_move_on_hold/product.py'
--- stock_move_on_hold/product.py	2013-07-11 19:26:45 +0000
+++ stock_move_on_hold/product.py	2013-11-01 13:48:45 +0000
@@ -19,9 +19,9 @@
 #
 #################################################################################
 
-from osv import fields, osv
-from tools.translate import _
-import decimal_precision as dp
+from openerp.osv import fields, osv
+from openerp.tools.translate import _
+import openerp.addons.decimal_precision as dp
 
 class product_product(osv.osv):
     

=== modified file 'stock_move_on_hold/stock.py'
--- stock_move_on_hold/stock.py	2012-03-07 12:56:37 +0000
+++ stock_move_on_hold/stock.py	2013-11-01 13:48:45 +0000
@@ -20,9 +20,9 @@
 #################################################################################
 
 from datetime import datetime
-from osv import fields, osv
-from tools.translate import _
-import netsvc
+from openerp.osv import fields, osv
+from openerp.tools.translate import _
+from openerp import netsvc
 
 # ----------------------------------------------------
 # Move

=== added directory 'stock_split_picking'
=== added file 'stock_split_picking/__init__.py'
--- stock_split_picking/__init__.py	1970-01-01 00:00:00 +0000
+++ stock_split_picking/__init__.py	2013-11-01 13:48:45 +0000
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    Author: Nicolas Bessi
+#    Copyright 2013 Camptocamp SA
+#
+#    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 <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+from . import model

=== added file 'stock_split_picking/__openerp__.py'
--- stock_split_picking/__openerp__.py	1970-01-01 00:00:00 +0000
+++ stock_split_picking/__openerp__.py	2013-11-01 13:48:45 +0000
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    Author: Nicolas Bessi, Guewen Baconnier
+#    Copyright 2013 Camptocamp SA
+#    Donors: donors
+#
+#    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 <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+{'name': 'Stock picking no confirm split',
+ 'version': 'version',
+ 'author': 'Camptocamp',
+ 'maintainer': 'Camptocamp',
+ 'category': 'stock',
+ 'complexity': "normal",  # easy, normal, expert
+ 'depends': ['stock'],
+ 'description': """
+Split picking without delivery
+------------------------------
+
+This addon adds a "Split" button on the out picking from header.
+It works like classical picking split (when you deliver) but does not pass backorder
+and backorder lines to state done.
+""",
+ 'website': 'http://www.camptocamp.com',
+ 'data': ['view/stock_partial_picking.xml'],
+ 'demo': [],
+ 'test': ['test/test_picking_split.yml'],
+ 'installable': True,
+ 'auto_install': False,
+ 'license': 'AGPL-3',
+ 'application': False,
+ }

=== added directory 'stock_split_picking/i18n'
=== added directory 'stock_split_picking/model'
=== added file 'stock_split_picking/model/__init__.py'
--- stock_split_picking/model/__init__.py	1970-01-01 00:00:00 +0000
+++ stock_split_picking/model/__init__.py	2013-11-01 13:48:45 +0000
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    Author: Nicolas Bessi
+#    Copyright 2013 Camptocamp SA
+#
+#    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 <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+from . import stock

=== added file 'stock_split_picking/model/stock.py'
--- stock_split_picking/model/stock.py	1970-01-01 00:00:00 +0000
+++ stock_split_picking/model/stock.py	2013-11-01 13:48:45 +0000
@@ -0,0 +1,198 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    Author: Nicolas Bessi, Guewen Baconnier
+#    Copyright 2013 Camptocamp SA
+#
+#    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 <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+"""Adds a split button on stock picking out to enable partial picking without
+   passing backorder state to done"""
+from openerp.osv import orm
+from openerp import netsvc
+
+
+class stock_picking_out(orm.Model):
+
+    _inherit = "stock.picking.out"
+
+    def split_process(self, cr, uid, ids, context=None):
+        """Use to trigger the wizard from button with
+           correct context"""
+        if context is None:
+            context = {}
+        """Open the partial picking wizard"""
+        ctx = context.copy()
+        ctx.update({
+            'active_model': self._name,
+            'active_ids': ids,
+            'active_id': ids[0] if ids else False,
+            'partial_no_confirm': True,
+        })
+        return {
+            'view_type': 'form',
+            'view_mode': 'form',
+            'res_model': 'stock.partial.picking',
+            'type': 'ir.actions.act_window',
+            'target': 'new',
+            'context': ctx,
+            'nodestroy': True,
+        }
+
+
+class stock_picking(orm.Model):
+    """Adds picking split without done state.
+    Long piece of code has to be copied from OpenERP
+    as there is no hook..."""
+
+    _inherit = "stock.picking"
+
+    def do_partial(self, cr, uid, ids, partial_datas, context=None):
+        """ Makes partial picking and moves done.
+
+        Inherited to allow the use of do_partial_no_confirm()
+        instead of do_partial(), switch is done with
+        a 'partial_no_confirm' key in the context.
+
+        @param partial_datas : Dictionary containing details of partial picking
+                          like partner_id, address_id, delivery_date,
+                          delivery moves with product_id, product_qty, uom
+        @return: Dictionary of values
+        """
+        if context is None:
+            context = {}
+        if context.get('partial_no_confirm'):
+            return self.do_partial_no_confirm(
+                cr, uid, ids, partial_datas, context=context)
+        else:
+            return super(stock_picking, self).do_partial(
+                cr, uid, ids, partial_datas, context=context)
+
+    def do_partial_no_confirm(self, cr, uid, ids, partial_datas, context=None):
+        """ Makes partial picking and moves but does not put them in done state.
+        Code is directly taken form OpenERP source code and not refactored.
+        @param partial_datas : Dictionary containing details of partial picking
+                          like partner_id, partner_id, delivery_date,
+                          delivery moves with product_id, product_qty, uom
+        @return: Dictionary of values
+        """
+        if context is None:
+            context = {}
+        else:
+            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")
+        for pick in self.browse(cr, uid, ids, context=context):
+            new_picking = None
+            complete, too_many, too_few = [], [], []
+            move_product_qty, prodlot_ids, product_avail, partial_qty, product_uoms = {}, {}, {}, {}, {}
+            for move in pick.move_lines:
+                if move.state in ('done', 'cancel'):
+                    continue
+                partial_data = partial_datas.get('move%s' % (move.id), {})
+                product_qty = partial_data.get('product_qty', 0.0)
+                move_product_qty[move.id] = product_qty
+                product_uom = partial_data.get('product_uom', False)
+                product_price = partial_data.get('product_price', 0.0)
+                product_currency = partial_data.get('product_currency', False)
+                prodlot_id = partial_data.get('prodlot_id')
+                prodlot_ids[move.id] = prodlot_id
+                product_uoms[move.id] = product_uom
+                partial_qty[move.id] = uom_obj._compute_qty(cr, uid, product_uoms[move.id],
+                                                            product_qty, move.product_uom.id)
+                if move.product_qty == partial_qty[move.id]:
+                    complete.append(move)
+                elif move.product_qty > partial_qty[move.id]:
+                    too_few.append(move)
+                else:
+                    too_many.append(move)
+            # We remove section of code that manage incoming picking
+            # please refere to original code if needed                                                                                                                                            
+            for move in too_few:
+                product_qty = move_product_qty[move.id]
+                if not new_picking:
+                    new_picking_name = pick.name
+                    self.write(cr, uid, [pick.id],
+                               {'name': sequence_obj.get(cr, uid,
+                                                         'stock.picking.%s' % pick.type),
+                               })
+                    new_picking = self.copy(cr, uid, pick.id,
+                            {
+                                'name': new_picking_name,
+                                'move_lines': [],
+                                'state': 'draft',
+                            })
+                if product_qty != 0:
+                    defaults = {
+                            'product_qty': product_qty,
+                            'product_uos_qty': product_qty,  # TODO: put correct uos_qty
+                            'picking_id': new_picking,
+                            'state': 'assigned',
+                            'move_dest_id': False,
+                            'price_unit': move.price_unit,
+                            'product_uom': product_uoms[move.id]
+                    }
+                    prodlot_id = prodlot_ids[move.id]
+                    if prodlot_id:
+                        defaults.update(prodlot_id=prodlot_id)
+                    move_obj.copy(cr, uid, move.id, defaults)
+                move_obj.write(cr, uid, [move.id],
+                        {
+                            'product_qty': move.product_qty - partial_qty[move.id],
+                            'product_uos_qty': move.product_qty - partial_qty[move.id], # TODO: put correct uos_qty
+                            'prodlot_id': False,
+                            'tracking_id': False,
+                        })
+
+            if new_picking:
+                move_obj.write(cr, uid, [c.id for c in complete], {'picking_id': new_picking})
+            for move in complete:
+                defaults = {'product_uom': product_uoms[move.id],
+                            'product_qty': move_product_qty[move.id]}
+                if prodlot_ids.get(move.id):
+                    defaults.update({'prodlot_id': prodlot_ids[move.id]})
+                move_obj.write(cr, uid, [move.id], defaults)
+            for move in too_many:
+                product_qty = move_product_qty[move.id]
+                defaults = {
+                    'product_qty': product_qty,
+                    'product_uos_qty': product_qty,  # TODO: put correct uos_qty
+                    'product_uom': product_uoms[move.id]
+                }
+                prodlot_id = prodlot_ids.get(move.id)
+                if prodlot_ids.get(move.id):
+                    defaults.update(prodlot_id=prodlot_id)
+                if new_picking:
+                    defaults.update(picking_id=new_picking)
+                move_obj.write(cr, uid, [move.id], defaults)
+
+            # At first we confirm the new picking (if necessary)
+            # We have removed here confirmation workflow calls
+            if new_picking:
+                wf_service.trg_validate(uid, 'stock.picking', new_picking, 'button_confirm', cr)
+                self.write(cr, uid, [pick.id], {'backorder_id': new_picking})
+                delivered_pack_id = new_picking
+            else:
+                delivered_pack_id = pick.id
+
+            delivered_pack = self.browse(cr, uid, delivered_pack_id, context=context)
+            res[pick.id] = {'delivered_picking': delivered_pack.id or False}
+
+        return res

=== added directory 'stock_split_picking/test'
=== added file 'stock_split_picking/test/test_picking_split.yml'
--- stock_split_picking/test/test_picking_split.yml	1970-01-01 00:00:00 +0000
+++ stock_split_picking/test/test_picking_split.yml	2013-11-01 13:48:45 +0000
@@ -0,0 +1,64 @@
+-
+  In order to test stock picking out spliting
+  I have to ensure when I split out picking, related backorder is not in state done
+-
+  I create a new GameBoy product for my tests
+-
+  !record {model: product.product, id: product_gameboy}:
+    categ_id: product.product_category_1
+    name: GameBoy
+    procure_method: make_to_stock
+    supply_method: buy
+    type: product
+    uom_id: product.product_uom_unit
+    uom_po_id: product.product_uom_unit
+    property_stock_inventory: stock.location_inventory
+    property_stock_procurement: stock.location_procurement
+    property_stock_production: stock.location_production
+-
+  I create a manual stock picking out
+-
+ !record {model: stock.picking, id: outgoing_shipment}:
+    type: out
+    location_dest_id: stock.stock_location_7
+-
+ !record {model: stock.move, id: outgoing_shipment_gameboy}:
+    company_id: base.main_company
+    picking_id: outgoing_shipment
+    product_id: product_gameboy
+    product_uom: product.product_uom_unit
+    product_qty: 130.0
+    product_uos_qty: 130.0
+    location_id: stock.stock_location_stock
+    location_dest_id: stock.stock_location_7
+-
+  Then I split my shippement in two 40/90
+-
+  !python {model: stock.partial.picking}: |
+    context.update({'active_model': 'stock.picking',
+                    'active_id': ref('outgoing_shipment'),
+                    'active_ids': [ref('outgoing_shipment')],
+                    'partial_no_confirm': True})
+-
+  !record {model: stock.partial.picking, id: partial_pick}:
+    move_ids:
+        - quantity: 40
+          product_id: product_gameboy
+          product_uom: product.product_uom_unit
+          move_id: outgoing_shipment_gameboy
+          location_id: stock.stock_location_stock
+          location_dest_id: stock.stock_location_7
+-
+  !python {model: stock.partial.picking }: |
+    self.do_partial(cr, uid, [ref('partial_pick')], context=context)
+-
+  I check that the backorder has 40 units with state set to assigned not done
+-
+  !python {model: stock.picking}: |
+    shipment = self.browse(cr, uid, ref("outgoing_shipment"))
+    backorder = shipment.backorder_id
+    assert backorder, "Backorder should be created after partial split."
+    assert backorder.state == 'assigned', "Backorder should not be close."
+    for move_line in backorder.move_lines:
+        assert move_line.product_qty == 40, "Qty in backorder does not correspond."
+        assert move_line.state == 'assigned', "Move line of backorder should not be closed."

=== added directory 'stock_split_picking/view'
=== added file 'stock_split_picking/view/stock_partial_picking.xml'
--- stock_split_picking/view/stock_partial_picking.xml	1970-01-01 00:00:00 +0000
+++ stock_split_picking/view/stock_partial_picking.xml	2013-11-01 13:48:45 +0000
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+  <data>
+    <record id="xml_id" model="ir.ui.view">
+      <field name="name">Stock picking out Split button</field>
+      <field name="model">stock.picking.out</field>
+      <field name="inherit_id" ref="stock.view_picking_out_form" />
+      <field name="arch" type="xml">
+        <field name ="state" position="before">
+          <button name="split_process"
+                  states="assigned"
+                  string="Split"
+                  groups="stock.group_stock_user"
+                  type="object"/>
+        </field>
+      </field>
+    </record>
+  </data>
+</openerp>


Follow ups