[MERGE] forward port of branch saas-3 up to c666030
[odoo/odoo.git] / addons / mrp / mrp.py
index fcaac08..1efefca 100644 (file)
 ##############################################################################
 
 import time
-from datetime import datetime
-
 import openerp.addons.decimal_precision as dp
 from openerp.osv import fields, osv, orm
-from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT, DATETIME_FORMATS_MAP
+from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
 from openerp.tools import float_compare
 from openerp.tools.translate import _
 from openerp import tools, SUPERUSER_ID
-from openerp import SUPERUSER_ID
 from openerp.addons.product import _common
 
+
+class mrp_property_group(osv.osv):
+    """
+    Group of mrp properties.
+    """
+    _name = 'mrp.property.group'
+    _description = 'Property Group'
+    _columns = {
+        'name': fields.char('Property Group', required=True),
+        'description': fields.text('Description'),
+    }
+
+class mrp_property(osv.osv):
+    """
+    Properties of mrp.
+    """
+    _name = 'mrp.property'
+    _description = 'Property'
+    _columns = {
+        'name': fields.char('Name', required=True),
+        'composition': fields.selection([('min','min'),('max','max'),('plus','plus')], 'Properties composition', required=True, help="Not used in computations, for information purpose only."),
+        'group_id': fields.many2one('mrp.property.group', 'Property Group', required=True),
+        'description': fields.text('Description'),
+    }
+    _defaults = {
+        'composition': lambda *a: 'min',
+    }
 #----------------------------------------------------------
 # Work Centers
 #----------------------------------------------------------
@@ -72,8 +96,6 @@ class mrp_workcenter(osv.osv):
             value = {'costs_hour': cost.standard_price}
         return {'value': value}
 
-
-
 class mrp_routing(osv.osv):
     """
     For specifying the routings of Work Centers.
@@ -81,12 +103,12 @@ class mrp_routing(osv.osv):
     _name = 'mrp.routing'
     _description = 'Routing'
     _columns = {
-        'name': fields.char('Name', size=64, required=True),
+        'name': fields.char('Name', required=True),
         'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the routing without removing it."),
         'code': fields.char('Code', size=8),
 
         'note': fields.text('Description'),
-        'workcenter_lines': fields.one2many('mrp.routing.workcenter', 'routing_id', 'Work Centers'),
+        'workcenter_lines': fields.one2many('mrp.routing.workcenter', 'routing_id', 'Work Centers', copy=True),
 
         'location_id': fields.many2one('stock.location', 'Production Location',
             help="Keep empty if you produce at the location where the finished products are needed." \
@@ -109,7 +131,7 @@ class mrp_routing_workcenter(osv.osv):
     _order = 'sequence'
     _columns = {
         'workcenter_id': fields.many2one('mrp.workcenter', 'Work Center', required=True),
-        'name': fields.char('Name', size=64, required=True),
+        'name': fields.char('Name', required=True),
         'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of routing Work Centers."),
         'cycle_nbr': fields.float('Number of Cycles', required=True,
             help="Number of iterations this work center has to do in the specified operation of the routing."),
@@ -133,256 +155,285 @@ class mrp_bom(osv.osv):
     _description = 'Bill of Material'
     _inherit = ['mail.thread']
 
-    def _child_compute(self, cr, uid, ids, name, arg, context=None):
-        """ Gets child bom.
-        @param self: The object pointer
-        @param cr: The current row, from the database cursor,
-        @param uid: The current user ID for security checks
-        @param ids: List of selected IDs
-        @param name: Name of the field
-        @param arg: User defined argument
-        @param context: A standard dictionary for contextual values
-        @return:  Dictionary of values
-        """
-        result = {}
-        if context is None:
-            context = {}
-        bom_obj = self.pool.get('mrp.bom')
-        bom_id = context and context.get('active_id', False) or False
-        cr.execute('select id from mrp_bom')
-        if all(bom_id != r[0] for r in cr.fetchall()):
-            ids.sort()
-            bom_id = ids[0]
-        bom_parent = bom_obj.browse(cr, uid, bom_id, context=context)
-        for bom in self.browse(cr, uid, ids, context=context):
-            if (bom_parent) or (bom.id == bom_id):
-                result[bom.id] = map(lambda x: x.id, bom.bom_lines)
-            else:
-                result[bom.id] = []
-            if bom.bom_lines:
-                continue
-            ok = ((name=='child_complete_ids') and (bom.product_id.supply_method=='produce'))
-            if (bom.type=='phantom' or ok):
-                sids = bom_obj.search(cr, uid, [('bom_id','=',False),('product_id','=',bom.product_id.id)])
-                if sids:
-                    bom2 = bom_obj.browse(cr, uid, sids[0], context=context)
-                    result[bom.id] += map(lambda x: x.id, bom2.bom_lines)
-
-        return result
-
-    def _compute_type(self, cr, uid, ids, field_name, arg, context=None):
-        """ Sets particular method for the selected bom type.
-        @param field_name: Name of the field
-        @param arg: User defined argument
-        @return:  Dictionary of values
-        """
-        res = dict.fromkeys(ids, False)
-        for line in self.browse(cr, uid, ids, context=context):
-            if line.type == 'phantom' and not line.bom_id:
-                res[line.id] = 'set'
-                continue
-            if line.bom_lines or line.type == 'phantom':
-                continue
-            if line.product_id.supply_method == 'produce':
-                if line.product_id.procure_method == 'make_to_stock':
-                    res[line.id] = 'stock'
-                else:
-                    res[line.id] = 'order'
-        return res
-
     _columns = {
-        'name': fields.char('Name', size=64),
+        'name': fields.char('Name'),
         'code': fields.char('Reference', size=16),
         'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the bills of material without removing it."),
-        'type': fields.selection([('normal','Normal BoM'),('phantom','Sets / Phantom')], 'BoM Type', required=True,
-                                 help= "If a by-product is used in several products, it can be useful to create its own BoM. "\
-                                 "Though if you don't want separated production orders for this by-product, select Set/Phantom as BoM type. "\
-                                 "If a Phantom BoM is used for a root product, it will be sold and shipped as a set of components, instead of being produced."),
-        'method': fields.function(_compute_type, string='Method', type='selection', selection=[('',''),('stock','On Stock'),('order','On Order'),('set','Set / Pack')]),
-        'date_start': fields.date('Valid From', help="Validity of this BoM or component. Keep empty if it's always valid."),
-        'date_stop': fields.date('Valid Until', help="Validity of this BoM or component. Keep empty if it's always valid."),
-        'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of bills of material."),
-        'position': fields.char('Internal Reference', size=64, help="Reference to a position in an external plan."),
-        'product_id': fields.many2one('product.product', 'Product', required=True),
-        'product_uos_qty': fields.float('Product UOS Qty'),
-        'product_uos': fields.many2one('product.uom', 'Product UOS', help="Product UOS (Unit of Sale) is the unit of measurement for the invoicing and promotion of stock."),
+        'type': fields.selection([('normal', 'Normal'), ('phantom', 'Set')], 'BoM Type', required=True,
+                help= "Set: When processing a sales order for this product, the delivery order will contain the raw materials, instead of the finished product."),
+        'position': fields.char('Internal Reference', help="Reference to a position in an external plan."),
+        'product_tmpl_id': fields.many2one('product.template', 'Product', domain="[('type', '!=', 'service')]", required=True),
+        'product_id': fields.many2one('product.product', 'Product Variant',
+            domain="['&', ('product_tmpl_id','=',product_tmpl_id), ('type','!=', 'service')]",
+            help="If a product variant is defined the BOM is available only for this product."),
+        'bom_line_ids': fields.one2many('mrp.bom.line', 'bom_id', 'BoM Lines', copy=True),
         'product_qty': fields.float('Product Quantity', required=True, digits_compute=dp.get_precision('Product Unit of Measure')),
         'product_uom': fields.many2one('product.uom', 'Product Unit of Measure', required=True, help="Unit of Measure (Unit of Measure) is the unit of measurement for the inventory control"),
+        'date_start': fields.date('Valid From', help="Validity of this BoM. Keep empty if it's always valid."),
+        'date_stop': fields.date('Valid Until', help="Validity of this BoM. Keep empty if it's always valid."),
+        'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of bills of material."),
+        'routing_id': fields.many2one('mrp.routing', 'Routing', help="The list of operations (list of work centers) to produce the finished product. "\
+                "The routing is mainly used to compute work center costs during operations and to plan future loads on work centers based on production planning."),
         'product_rounding': fields.float('Product Rounding', help="Rounding applied on the product quantity."),
-        'product_efficiency': fields.float('Manufacturing Efficiency', required=True, help="A factor of 0.9 means a loss of 10% within the production process."),
-        'bom_lines': fields.one2many('mrp.bom', 'bom_id', 'BoM Lines'),
-        'bom_id': fields.many2one('mrp.bom', 'Parent BoM', ondelete='cascade', select=True),
-        'routing_id': fields.many2one('mrp.routing', 'Routing', help="The list of operations (list of work centers) to produce the finished product. The routing is mainly used to compute work center costs during operations and to plan future loads on work centers based on production planning."),
-        'property_ids': fields.many2many('mrp.property', 'mrp_bom_property_rel', 'bom_id','property_id', 'Properties'),
-        'child_complete_ids': fields.function(_child_compute, relation='mrp.bom', string="BoM Hierarchy", type='many2many'),
-        'company_id': fields.many2one('res.company','Company',required=True),
+        'product_efficiency': fields.float('Manufacturing Efficiency', required=True, help="A factor of 0.9 means a loss of 10% during the production process."),
+        'property_ids': fields.many2many('mrp.property', string='Properties'),
+        'company_id': fields.many2one('res.company', 'Company', required=True),
     }
+
+    def _get_uom_id(self, cr, uid, *args):
+        return self.pool["product.uom"].search(cr, uid, [], limit=1, order='id')[0]
     _defaults = {
         'active': lambda *a: 1,
-        'product_efficiency': lambda *a: 1.0,
         'product_qty': lambda *a: 1.0,
+        'product_efficiency': lambda *a: 1.0,
         'product_rounding': lambda *a: 0.0,
         'type': lambda *a: 'normal',
-        'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'mrp.bom', context=c),
+        'product_uom': _get_uom_id,
+        'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'mrp.bom', context=c),
     }
     _order = "sequence"
-    _parent_name = "bom_id"
-    _sql_constraints = [
-        ('bom_qty_zero', 'CHECK (product_qty>0)',  'All product quantities must be greater than 0.\n' \
-            'You should install the mrp_byproduct module if you want to manage extra products on BoMs !'),
-    ]
-
-    def _check_recursion(self, cr, uid, ids, context=None):
-        level = 100
-        while len(ids):
-            cr.execute('select distinct bom_id from mrp_bom where id IN %s',(tuple(ids),))
-            ids = filter(None, map(lambda x:x[0], cr.fetchall()))
-            if not level:
-                return False
-            level -= 1
-        return True
-
-    def _check_product(self, cr, uid, ids, context=None):
-        all_prod = []
-        boms = self.browse(cr, uid, ids, context=context)
-        def check_bom(boms):
-            res = True
-            for bom in boms:
-                if bom.product_id.id in all_prod:
-                    res = res and False
-                all_prod.append(bom.product_id.id)
-                lines = bom.bom_lines
-                if lines:
-                    res = res and check_bom([bom_id for bom_id in lines if bom_id not in boms])
-            return res
-        return check_bom(boms)
-
-    _constraints = [
-        (_check_recursion, 'Error ! You cannot create recursive BoM.', ['parent_id']),
-        (_check_product, 'BoM line product should not be same as BoM product.', ['product_id']),
-    ]
-
-    def onchange_product_id(self, cr, uid, ids, product_id, name, context=None):
-        """ Changes UoM and name if product_id changes.
-        @param name: Name of the field
-        @param product_id: Changed product_id
-        @return:  Dictionary of changed values
-        """
-        if product_id:
-            prod = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
-            return {'value': {'name': prod.name, 'product_uom': prod.uom_id.id}}
-        return {}
-
-    def onchange_uom(self, cr, uid, ids, product_id, product_uom, context=None):
-        res = {'value':{}}
-        if not product_uom or not product_id:
-            return res
-        product = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
-        uom = self.pool.get('product.uom').browse(cr, uid, product_uom, context=context)
-        if uom.category_id.id != product.uom_id.category_id.id:
-            res['warning'] = {'title': _('Warning'), 'message': _('The Product Unit of Measure you chose has a different category than in the product form.')}
-            res['value'].update({'product_uom': product.uom_id.id})
-        return res
 
-    def _bom_find(self, cr, uid, product_id, product_uom, properties=None):
+    def _bom_find(self, cr, uid, product_tmpl_id=None, product_id=None, properties=None, context=None):
         """ Finds BoM for particular product and product uom.
-        @param product_id: Selected product.
+        @param product_tmpl_id: Selected product.
         @param product_uom: Unit of measure of a product.
         @param properties: List of related properties.
         @return: False or BoM id.
         """
         if properties is None:
             properties = []
-        domain = [('product_id', '=', product_id), ('bom_id', '=', False),
-                   '|', ('date_start', '=', False), ('date_start', '<=', time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)),
-                   '|', ('date_stop', '=', False), ('date_stop', '>=', time.strftime(DEFAULT_SERVER_DATETIME_FORMAT))]
-        ids = self.search(cr, uid, domain)
-        max_prop = 0
-        result = False
-        for bom in self.pool.get('mrp.bom').browse(cr, uid, ids):
-            prop = 0
-            for prop_id in bom.property_ids:
-                if prop_id.id in properties:
-                    prop += 1
-            if (prop > max_prop) or ((max_prop == 0) and not result):
-                result = bom.id
-                max_prop = prop
-        return result
+        if product_id:
+            if not product_tmpl_id:
+                product_tmpl_id = self.pool['product.product'].browse(cr, uid, product_id, context=context).product_tmpl_id.id
+            domain = [
+                '|',
+                    ('product_id', '=', product_id),
+                    '&',
+                        ('product_id', '=', False),
+                        ('product_tmpl_id', '=', product_tmpl_id)
+            ]
+        elif product_tmpl_id:
+            domain = [('product_id', '=', False), ('product_tmpl_id', '=', product_tmpl_id)]
+        else:
+            # neither product nor template, makes no sense to search
+            return False
+        domain = domain + [ '|', ('date_start', '=', False), ('date_start', '<=', time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)),
+                            '|', ('date_stop', '=', False), ('date_stop', '>=', time.strftime(DEFAULT_SERVER_DATETIME_FORMAT))]
+        # order to prioritize bom with product_id over the one without
+        ids = self.search(cr, uid, domain, order='product_id', context=context)
+        # Search a BoM which has all properties specified, or if you can not find one, you could
+        # pass a BoM without any properties
+        bom_empty_prop = False
+        for bom in self.pool.get('mrp.bom').browse(cr, uid, ids, context=context):
+            if not set(map(int, bom.property_ids or [])) - set(properties or []):
+                if properties and not bom.property_ids:
+                    bom_empty_prop = bom.id
+                else:
+                    return bom.id
+        return bom_empty_prop
 
-    def _bom_explode(self, cr, uid, bom, factor, properties=None, addthis=False, level=0, routing_id=False):
+    def _bom_explode(self, cr, uid, bom, product, factor, properties=None, level=0, routing_id=False, previous_products=None, master_bom=None, context=None):
         """ Finds Products and Work Centers for related BoM for manufacturing order.
-        @param bom: BoM of particular product.
-        @param factor: Factor of product UoM.
+        @param bom: BoM of particular product template.
+        @param product: Select a particular variant of the BoM. If False use BoM without variants.
+        @param factor: Factor represents the quantity, but in UoM of the BoM, taking into account the numbers produced by the BoM
         @param properties: A List of properties Ids.
-        @param addthis: If BoM found then True else False.
         @param level: Depth level to find BoM lines starts from 10.
+        @param previous_products: List of product previously use by bom explore to avoid recursion
+        @param master_bom: When recursion, used to display the name of the master bom
         @return: result: List of dictionaries containing product details.
                  result2: List of dictionaries containing Work Center details.
         """
+        uom_obj = self.pool.get("product.uom")
         routing_obj = self.pool.get('mrp.routing')
-        factor = factor / (bom.product_efficiency or 1.0)
-        factor = _common.ceiling(factor, bom.product_rounding)
-        if factor < bom.product_rounding:
-            factor = bom.product_rounding
+        master_bom = master_bom or bom
+
+
+        def _factor(factor, product_efficiency, product_rounding):
+            factor = factor / (product_efficiency or 1.0)
+            factor = _common.ceiling(factor, product_rounding)
+            if factor < product_rounding:
+                factor = product_rounding
+            return factor
+
+        factor = _factor(factor, bom.product_efficiency, bom.product_rounding)
+
         result = []
         result2 = []
-        phantom = False
-        if bom.type == 'phantom' and not bom.bom_lines:
-            newbom = self._bom_find(cr, uid, bom.product_id.id, bom.product_uom.id, properties)
 
-            if newbom:
-                res = self._bom_explode(cr, uid, self.browse(cr, uid, [newbom])[0], factor*bom.product_qty, properties, addthis=True, level=level+10)
-                result = result + res[0]
-                result2 = result2 + res[1]
-                phantom = True
-            else:
-                phantom = False
-        if not phantom:
-            if addthis and not bom.bom_lines:
-                result.append(
-                {
-                    'name': bom.product_id.name,
-                    'product_id': bom.product_id.id,
-                    'product_qty': bom.product_qty * factor,
-                    'product_uom': bom.product_uom.id,
-                    'product_uos_qty': bom.product_uos and bom.product_uos_qty * factor or False,
-                    'product_uos': bom.product_uos and bom.product_uos.id or False,
+        routing = (routing_id and routing_obj.browse(cr, uid, routing_id)) or bom.routing_id or False
+        if routing:
+            for wc_use in routing.workcenter_lines:
+                wc = wc_use.workcenter_id
+                d, m = divmod(factor, wc_use.workcenter_id.capacity_per_cycle)
+                mult = (d + (m and 1.0 or 0.0))
+                cycle = mult * wc_use.cycle_nbr
+                result2.append({
+                    'name': tools.ustr(wc_use.name) + ' - ' + tools.ustr(bom.product_tmpl_id.name_get()[0][1]),
+                    'workcenter_id': wc.id,
+                    'sequence': level + (wc_use.sequence or 0),
+                    'cycle': cycle,
+                    'hour': float(wc_use.hour_nbr * mult + ((wc.time_start or 0.0) + (wc.time_stop or 0.0) + cycle * (wc.time_cycle or 0.0)) * (wc.time_efficiency or 1.0)),
                 })
-            routing = (routing_id and routing_obj.browse(cr, uid, routing_id)) or bom.routing_id or False
-            if routing:
-                for wc_use in routing.workcenter_lines:
-                    wc = wc_use.workcenter_id
-                    d, m = divmod(factor, wc_use.workcenter_id.capacity_per_cycle)
-                    mult = (d + (m and 1.0 or 0.0))
-                    cycle = mult * wc_use.cycle_nbr
-                    result2.append({
-                        'name': tools.ustr(wc_use.name) + ' - '  + tools.ustr(bom.product_id.name),
-                        'workcenter_id': wc.id,
-                        'sequence': level+(wc_use.sequence or 0),
-                        'cycle': cycle,
-                        'hour': float(wc_use.hour_nbr*mult + ((wc.time_start or 0.0)+(wc.time_stop or 0.0)+cycle*(wc.time_cycle or 0.0)) * (wc.time_efficiency or 1.0)),
-                    })
-            for bom2 in bom.bom_lines:
-                res = self._bom_explode(cr, uid, bom2, factor, properties, addthis=True, level=level+10)
+
+        for bom_line_id in bom.bom_line_ids:
+            if bom_line_id.date_start and bom_line_id.date_start > time.strftime(DEFAULT_SERVER_DATETIME_FORMAT) or \
+                bom_line_id.date_stop and bom_line_id.date_stop < time.strftime(DEFAULT_SERVER_DATETIME_FORMAT):
+                    continue
+            # all bom_line_id variant values must be in the product
+            if bom_line_id.attribute_value_ids:
+                if not product or (set(map(int,bom_line_id.attribute_value_ids or [])) - set(map(int,product.attribute_value_ids))):
+                    continue
+
+            if previous_products and bom_line_id.product_id.product_tmpl_id.id in previous_products:
+                raise osv.except_osv(_('Invalid Action!'), _('BoM "%s" contains a BoM line with a product recursion: "%s".') % (master_bom.name,bom_line_id.product_id.name_get()[0][1]))
+
+            quantity = _factor(bom_line_id.product_qty * factor, bom_line_id.product_efficiency, bom_line_id.product_rounding)
+            bom_id = self._bom_find(cr, uid, product_id=bom_line_id.product_id.id, properties=properties, context=context)
+
+            #If BoM should not behave like PhantoM, just add the product, otherwise explode further
+            if bom_line_id.type != "phantom" and (not bom_id or self.browse(cr, uid, bom_id, context=context).type != "phantom"):
+                result.append({
+                    'name': bom_line_id.product_id.name,
+                    'product_id': bom_line_id.product_id.id,
+                    'product_qty': quantity,
+                    'product_uom': bom_line_id.product_uom.id,
+                    'product_uos_qty': bom_line_id.product_uos and _factor(bom_line_id.product_uos_qty * factor, bom_line_id.product_efficiency, bom_line_id.product_rounding) or False,
+                    'product_uos': bom_line_id.product_uos and bom_line_id.product_uos.id or False,
+                })
+            elif bom_id:
+                all_prod = [bom.product_tmpl_id.id] + (previous_products or [])
+                bom2 = self.browse(cr, uid, bom_id, context=context)
+                # We need to convert to units/UoM of chosen BoM
+                factor2 = uom_obj._compute_qty(cr, uid, bom_line_id.product_uom.id, quantity, bom2.product_uom.id)
+                quantity2 = factor2 / bom2.product_qty
+                res = self._bom_explode(cr, uid, bom2, bom_line_id.product_id, quantity2,
+                    properties=properties, level=level + 10, previous_products=all_prod, master_bom=master_bom, context=context)
                 result = result + res[0]
                 result2 = result2 + res[1]
+            else:
+                raise osv.except_osv(_('Invalid Action!'), _('BoM "%s" contains a phantom BoM line but the product "%s" does not have any BoM defined.') % (master_bom.name,bom_line_id.product_id.name_get()[0][1]))
+
         return result, result2
 
     def copy_data(self, cr, uid, id, default=None, context=None):
         if default is None:
             default = {}
         bom_data = self.read(cr, uid, id, [], context=context)
-        default.update(name=_("%s (copy)") % (bom_data['name']), bom_id=False)
+        default.update(name=_("%s (copy)") % (bom_data['name']))
         return super(mrp_bom, self).copy_data(cr, uid, id, default, context=context)
 
+    def onchange_uom(self, cr, uid, ids, product_tmpl_id, product_uom, context=None):
+        res = {'value': {}}
+        if not product_uom or not product_tmpl_id:
+            return res
+        product = self.pool.get('product.template').browse(cr, uid, product_tmpl_id, context=context)
+        uom = self.pool.get('product.uom').browse(cr, uid, product_uom, context=context)
+        if uom.category_id.id != product.uom_id.category_id.id:
+            res['warning'] = {'title': _('Warning'), 'message': _('The Product Unit of Measure you chose has a different category than in the product form.')}
+            res['value'].update({'product_uom': product.uom_id.id})
+        return res
+
+    def onchange_product_tmpl_id(self, cr, uid, ids, product_tmpl_id, product_qty=0, context=None):
+        """ Changes UoM and name if product_id changes.
+        @param product_id: Changed product_id
+        @return:  Dictionary of changed values
+        """
+        res = {}
+        if product_tmpl_id:
+            prod = self.pool.get('product.template').browse(cr, uid, product_tmpl_id, context=context)
+            res['value'] = {
+                'name': prod.name,
+                'product_uom': prod.uom_id.id,
+            }
+        return res
+
+class mrp_bom_line(osv.osv):
+    _name = 'mrp.bom.line'
+    _order = "sequence"
+
+    def _get_child_bom_lines(self, cr, uid, ids, field_name, arg, context=None):
+        """If the BOM line refers to a BOM, return the ids of the child BOM lines"""
+        bom_obj = self.pool['mrp.bom']
+        res = {}
+        for bom_line in self.browse(cr, uid, ids, context=context):
+            bom_id = bom_obj._bom_find(cr, uid,
+                product_tmpl_id=bom_line.product_id.product_tmpl_id.id,
+                product_id=bom_line.product_id.id, context=context)
+            if bom_id:
+                child_bom = bom_obj.browse(cr, uid, bom_id, context=context)
+                res[bom_line.id] = [x.id for x in child_bom.bom_line_ids]
+            else:
+                res[bom_line.id] = False
+        return res
+
+    _columns = {
+        'type': fields.selection([('normal', 'Normal'), ('phantom', 'Phantom')], 'BoM Line Type', required=True,
+                help="Phantom: this product line will not appear in the raw materials of manufacturing orders,"
+                     "it will be directly replaced by the raw materials of its own BoM, without triggering"
+                     "an extra manufacturing order."),
+        'product_id': fields.many2one('product.product', 'Product', required=True),
+        'product_uos_qty': fields.float('Product UOS Qty'),
+        'product_uos': fields.many2one('product.uom', 'Product UOS', help="Product UOS (Unit of Sale) is the unit of measurement for the invoicing and promotion of stock."),
+        'product_qty': fields.float('Product Quantity', required=True, digits_compute=dp.get_precision('Product Unit of Measure')),
+        'product_uom': fields.many2one('product.uom', 'Product Unit of Measure', required=True,
+            help="Unit of Measure (Unit of Measure) is the unit of measurement for the inventory control"),
+        
+        'date_start': fields.date('Valid From', help="Validity of component. Keep empty if it's always valid."),
+        'date_stop': fields.date('Valid Until', help="Validity of component. Keep empty if it's always valid."),
+        'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying."),
+        'routing_id': fields.many2one('mrp.routing', 'Routing', help="The list of operations (list of work centers) to produce the finished product. The routing is mainly used to compute work center costs during operations and to plan future loads on work centers based on production planning."),
+        'product_rounding': fields.float('Product Rounding', help="Rounding applied on the product quantity."),
+        'product_efficiency': fields.float('Manufacturing Efficiency', required=True, help="A factor of 0.9 means a loss of 10% within the production process."),
+        'property_ids': fields.many2many('mrp.property', string='Properties'), #Not used
+
+        'bom_id': fields.many2one('mrp.bom', 'Parent BoM', ondelete='cascade', select=True, required=True),
+        'attribute_value_ids': fields.many2many('product.attribute.value', string='Variants', help="BOM Product Variants needed form apply this line."),
+        'child_line_ids': fields.function(_get_child_bom_lines, relation="mrp.bom.line", string="BOM lines of the referred bom", type="one2many")
+    }
+
+    def _get_uom_id(self, cr, uid, *args):
+        return self.pool["product.uom"].search(cr, uid, [], limit=1, order='id')[0]
+    _defaults = {
+        'product_qty': lambda *a: 1.0,
+        'product_efficiency': lambda *a: 1.0,
+        'product_rounding': lambda *a: 0.0,
+        'type': lambda *a: 'normal',
+        'product_uom': _get_uom_id,
+    }
+    _sql_constraints = [
+        ('bom_qty_zero', 'CHECK (product_qty>0)', 'All product quantities must be greater than 0.\n' \
+            'You should install the mrp_byproduct module if you want to manage extra products on BoMs !'),
+    ]
+
+    def onchange_uom(self, cr, uid, ids, product_id, product_uom, context=None):
+        res = {'value': {}}
+        if not product_uom or not product_id:
+            return res
+        product = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
+        uom = self.pool.get('product.uom').browse(cr, uid, product_uom, context=context)
+        if uom.category_id.id != product.uom_id.category_id.id:
+            res['warning'] = {'title': _('Warning'), 'message': _('The Product Unit of Measure you chose has a different category than in the product form.')}
+            res['value'].update({'product_uom': product.uom_id.id})
+        return res
 
-def rounding(f, r):
-    # TODO for trunk: log deprecation warning
-    # _logger.warning("Deprecated rounding method, please use tools.float_round to round floats.")
-    import math
-    if not r:
-        return f
-    return math.ceil(f / r) * r
+    def onchange_product_id(self, cr, uid, ids, product_id, product_qty=0, context=None):
+        """ Changes UoM if product_id changes.
+        @param product_id: Changed product_id
+        @return:  Dictionary of changed values
+        """
+        res = {}
+        if product_id:
+            prod = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
+            res['value'] = {
+                'product_uom': prod.uom_id.id,
+                'product_uos_qty': 0,
+                'product_uos': False
+            }
+            if prod.uos_id.id:
+                res['value']['product_uos_qty'] = product_qty * prod.uos_coeff
+                res['value']['product_uos'] = prod.uos_id.id
+        return res
 
 class mrp_production(osv.osv):
     """
@@ -390,7 +441,7 @@ class mrp_production(osv.osv):
     """
     _name = 'mrp.production'
     _description = 'Manufacturing Order'
-    _date_name  = 'date_planned'
+    _date_name = 'date_planned'
     _inherit = ['mail.thread', 'ir.needaction_mixin']
 
     def _production_calc(self, cr, uid, ids, prop, unknow_none, context=None):
@@ -438,15 +489,33 @@ class mrp_production(osv.osv):
                 result[mrp_production.id] = done / mrp_production.product_qty * 100
         return result
 
+    def _moves_assigned(self, cr, uid, ids, name, arg, context=None):
+        """ Test whether all the consume lines are assigned """
+        res = {}
+        for production in self.browse(cr, uid, ids, context=context):
+            res[production.id] = True
+            states = [x.state != 'assigned' for x in production.move_lines if x]
+            if any(states) or len(states) == 0: #When no moves, ready_production will be False, but test_ready will pass
+                res[production.id] = False
+        return res
+
+    def _mrp_from_move(self, cr, uid, ids, context=None):
+        """ Return mrp"""
+        res = []
+        for move in self.browse(cr, uid, ids, context=context):
+            res += self.pool.get("mrp.production").search(cr, uid, [('move_lines', 'in', move.id)], context=context)
+        return res
+
     _columns = {
-        'name': fields.char('Reference', size=64, required=True, readonly=True, states={'draft': [('readonly', False)]}),
-        'origin': fields.char('Source Document', size=64, readonly=True, states={'draft': [('readonly', False)]},
-            help="Reference of the document that generated this production order request."),
-        'priority': fields.selection([('0','Not urgent'),('1','Normal'),('2','Urgent'),('3','Very Urgent')], 'Priority',
+        'name': fields.char('Reference', required=True, readonly=True, states={'draft': [('readonly', False)]}, copy=False),
+        'origin': fields.char('Source Document', readonly=True, states={'draft': [('readonly', False)]},
+            help="Reference of the document that generated this production order request.", copy=False),
+        'priority': fields.selection([('0', 'Not urgent'), ('1', 'Normal'), ('2', 'Urgent'), ('3', 'Very Urgent')], 'Priority',
             select=True, readonly=True, states=dict.fromkeys(['draft', 'confirmed'], [('readonly', False)])),
 
-        'product_id': fields.many2one('product.product', 'Product', required=True, readonly=True, states={'draft': [('readonly', False)]}),
-        'product_qty': fields.float('Product Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True, readonly=True, states={'draft':[('readonly',False)]}),
+        'product_id': fields.many2one('product.product', 'Product', required=True, readonly=True, states={'draft': [('readonly', False)]}, 
+                                      domain=[('type','!=','service')]),
+        'product_qty': fields.float('Product Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True, readonly=True, states={'draft': [('readonly', False)]}),
         'product_uom': fields.many2one('product.uom', 'Product Unit of Measure', required=True, readonly=True, states={'draft': [('readonly', False)]}),
         'product_uos_qty': fields.float('Product UoS Quantity', readonly=True, states={'draft': [('readonly', False)]}),
         'product_uos': fields.many2one('product.uom', 'Product UoS', readonly=True, states={'draft': [('readonly', False)]}),
@@ -454,38 +523,36 @@ class mrp_production(osv.osv):
             string='Production progress'),
 
         'location_src_id': fields.many2one('stock.location', 'Raw Materials Location', required=True,
-            readonly=True, states={'draft':[('readonly',False)]},
+            readonly=True, states={'draft': [('readonly', False)]},
             help="Location where the system will look for components."),
         'location_dest_id': fields.many2one('stock.location', 'Finished Products Location', required=True,
-            readonly=True, states={'draft':[('readonly',False)]},
+            readonly=True, states={'draft': [('readonly', False)]},
             help="Location where the system will stock the finished products."),
-        'date_planned': fields.datetime('Scheduled Date', required=True, select=1, readonly=True, states={'draft':[('readonly',False)]}),
-        'date_start': fields.datetime('Start Date', select=True, readonly=True),
-        'date_finished': fields.datetime('End Date', select=True, readonly=True),
-        'bom_id': fields.many2one('mrp.bom', 'Bill of Material', domain=[('bom_id','=',False)], readonly=True, states={'draft':[('readonly',False)]},
+        'date_planned': fields.datetime('Scheduled Date', required=True, select=1, readonly=True, states={'draft': [('readonly', False)]}, copy=False),
+        'date_start': fields.datetime('Start Date', select=True, readonly=True, copy=False),
+        'date_finished': fields.datetime('End Date', select=True, readonly=True, copy=False),
+        'bom_id': fields.many2one('mrp.bom', 'Bill of Material', readonly=True, states={'draft': [('readonly', False)]},
             help="Bill of Materials allow you to define the list of required raw materials to make a finished product."),
-        'routing_id': fields.many2one('mrp.routing', string='Routing', on_delete='set null', readonly=True, states={'draft':[('readonly',False)]},
+        'routing_id': fields.many2one('mrp.routing', string='Routing', on_delete='set null', readonly=True, states={'draft': [('readonly', False)]},
             help="The list of operations (list of work centers) to produce the finished product. The routing is mainly used to compute work center costs during operations and to plan future loads on work centers based on production plannification."),
-        'picking_id': fields.many2one('stock.picking', 'Picking List', readonly=True, ondelete="restrict",
-            help="This is the Internal Picking List that brings the finished product to the production plan"),
-        'move_prod_id': fields.many2one('stock.move', 'Product Move', readonly=True),
-        'move_lines': fields.many2many('stock.move', 'mrp_production_move_ids', 'production_id', 'move_id', 'Products to Consume',
-            domain=[('state','not in', ('done', 'cancel'))], readonly=True, states={'draft':[('readonly',False)]}),
-        'move_lines2': fields.many2many('stock.move', 'mrp_production_move_ids', 'production_id', 'move_id', 'Consumed Products',
-            domain=[('state','in', ('done', 'cancel'))], readonly=True, states={'draft':[('readonly',False)]}),
+        'move_prod_id': fields.many2one('stock.move', 'Product Move', readonly=True, copy=False),
+        'move_lines': fields.one2many('stock.move', 'raw_material_production_id', 'Products to Consume',
+            domain=[('state', 'not in', ('done', 'cancel'))], readonly=True, states={'draft': [('readonly', False)]}),
+        'move_lines2': fields.one2many('stock.move', 'raw_material_production_id', 'Consumed Products',
+            domain=[('state', 'in', ('done', 'cancel'))], readonly=True),
         'move_created_ids': fields.one2many('stock.move', 'production_id', 'Products to Produce',
-            domain=[('state','not in', ('done', 'cancel'))], readonly=True, states={'draft':[('readonly',False)]}),
+            domain=[('state', 'not in', ('done', 'cancel'))], readonly=True),
         'move_created_ids2': fields.one2many('stock.move', 'production_id', 'Produced Products',
-            domain=[('state','in', ('done', 'cancel'))], readonly=True, states={'draft':[('readonly',False)]}),
+            domain=[('state', 'in', ('done', 'cancel'))], readonly=True),
         'product_lines': fields.one2many('mrp.production.product.line', 'production_id', 'Scheduled goods',
-            readonly=True, states={'draft':[('readonly',False)]}),
+            readonly=True),
         'workcenter_lines': fields.one2many('mrp.production.workcenter.line', 'production_id', 'Work Centers Utilisation',
-            readonly=True, states={'draft':[('readonly',False)]}),
+            readonly=True, states={'draft': [('readonly', False)]}),
         'state': fields.selection(
-            [('draft', 'New'), ('cancel', 'Cancelled'), ('picking_except', 'Picking Exception'), ('confirmed', 'Awaiting Raw Materials'),
+            [('draft', 'New'), ('cancel', 'Cancelled'), ('confirmed', 'Awaiting Raw Materials'),
                 ('ready', 'Ready to Produce'), ('in_production', 'Production Started'), ('done', 'Done')],
             string='Status', readonly=True,
-            track_visibility='onchange',
+            track_visibility='onchange', copy=False,
             help="When the production order is created the status is set to 'Draft'.\n\
                 If the order is confirmed the status is set to 'Waiting Goods'.\n\
                 If any exceptions are there, the status is set to 'Picking Exception'.\n\
@@ -494,24 +561,28 @@ class mrp_production(osv.osv):
                 When the production is over, the status is set to 'Done'."),
         'hour_total': fields.function(_production_calc, type='float', string='Total Hours', multi='workorder', store=True),
         'cycle_total': fields.function(_production_calc, type='float', string='Total Cycles', multi='workorder', store=True),
-        'user_id':fields.many2one('res.users', 'Responsible'),
-        'company_id': fields.many2one('res.company','Company',required=True),
+        'user_id': fields.many2one('res.users', 'Responsible'),
+        'company_id': fields.many2one('res.company', 'Company', required=True),
+        'ready_production': fields.function(_moves_assigned, type='boolean', store={'stock.move': (_mrp_from_move, ['state'], 10)}),
     }
+
     _defaults = {
         'priority': lambda *a: '1',
         'state': lambda *a: 'draft',
         'date_planned': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
-        'product_qty':  lambda *a: 1.0,
+        'product_qty': lambda *a: 1.0,
         'user_id': lambda self, cr, uid, c: uid,
         'name': lambda x, y, z, c: x.pool.get('ir.sequence').get(y, z, 'mrp.production') or '/',
         'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'mrp.production', context=c),
         'location_src_id': _src_id_default,
         'location_dest_id': _dest_id_default
     }
+
     _sql_constraints = [
         ('name_uniq', 'unique(name, company_id)', 'Reference must be unique per Company!'),
     ]
-    _order = 'priority desc, date_planned asc';
+
+    _order = 'priority desc, date_planned asc'
 
     def _check_qty(self, cr, uid, ids, context=None):
         for order in self.browse(cr, uid, ids, context=context):
@@ -529,21 +600,6 @@ class mrp_production(osv.osv):
                 raise osv.except_osv(_('Invalid Action!'), _('Cannot delete a manufacturing order in state \'%s\'.') % production.state)
         return super(mrp_production, self).unlink(cr, uid, ids, context=context)
 
-    def copy(self, cr, uid, id, default=None, context=None):
-        if default is None:
-            default = {}
-        default.update({
-            'name': self.pool.get('ir.sequence').get(cr, uid, 'mrp.production'),
-            'move_lines' : [],
-            'move_lines2' : [],
-            'move_created_ids' : [],
-            'move_created_ids2' : [],
-            'product_lines' : [],
-            'move_prod_id' : False,
-            'picking_id' : False
-        })
-        return super(mrp_production, self).copy(cr, uid, id, default, context)
-
     def location_id_change(self, cr, uid, ids, src, dest, context=None):
         """ Changes destination location if source location is changed.
         @param src: Source location id.
@@ -556,32 +612,33 @@ class mrp_production(osv.osv):
             return {'value': {'location_dest_id': src}}
         return {}
 
-    def product_id_change(self, cr, uid, ids, product_id, context=None):
+    def product_id_change(self, cr, uid, ids, product_id, product_qty=0, context=None):
         """ Finds UoM of changed product.
         @param product_id: Id of changed product.
         @return: Dictionary of values.
         """
+        result = {}
         if not product_id:
             return {'value': {
                 'product_uom': False,
                 'bom_id': False,
-                'routing_id': False
+                'routing_id': False,
+                'product_uos_qty': 0,
+                'product_uos': False
             }}
         bom_obj = self.pool.get('mrp.bom')
         product = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
-        bom_id = bom_obj._bom_find(cr, uid, product.id, product.uom_id and product.uom_id.id, [])
+        bom_id = bom_obj._bom_find(cr, uid, product_id=product.id, properties=[], context=context)
         routing_id = False
         if bom_id:
             bom_point = bom_obj.browse(cr, uid, bom_id, context=context)
             routing_id = bom_point.routing_id.id or False
-
         product_uom_id = product.uom_id and product.uom_id.id or False
-        result = {
-            'product_uom': product_uom_id,
-            'bom_id': bom_id,
-            'routing_id': routing_id,
-        }
-        return {'value': result}
+        result['value'] = {'product_uos_qty': 0, 'product_uos': False, 'product_uom': product_uom_id, 'bom_id': bom_id, 'routing_id': routing_id}
+        if product.uos_id.id:
+            result['value']['product_uos_qty'] = product_qty * product.uos_coeff
+            result['value']['product_uos'] = product.uos_id.id
+        return result
 
     def bom_id_change(self, cr, uid, ids, bom_id, context=None):
         """ Finds routing for changed BoM.
@@ -599,18 +656,11 @@ class mrp_production(osv.osv):
         }
         return {'value': result}
 
-    def action_picking_except(self, cr, uid, ids):
-        """ Changes the state to Exception.
-        @return: True
-        """
-        self.write(cr, uid, ids, {'state': 'picking_except'})
-        return True
-    
+
     def _action_compute_lines(self, cr, uid, ids, properties=None, context=None):
         """ Compute product_lines and workcenter_lines from BoM structure
         @return: product_lines
         """
-
         if properties is None:
             properties = []
         results = []
@@ -618,19 +668,16 @@ class mrp_production(osv.osv):
         uom_obj = self.pool.get('product.uom')
         prod_line_obj = self.pool.get('mrp.production.product.line')
         workcenter_line_obj = self.pool.get('mrp.production.workcenter.line')
-
         for production in self.browse(cr, uid, ids, context=context):
             #unlink product_lines
             prod_line_obj.unlink(cr, SUPERUSER_ID, [line.id for line in production.product_lines], context=context)
-    
             #unlink workcenter_lines
             workcenter_line_obj.unlink(cr, SUPERUSER_ID, [line.id for line in production.workcenter_lines], context=context)
-    
             # search BoM structure and route
             bom_point = production.bom_id
             bom_id = production.bom_id.id
             if not bom_point:
-                bom_id = bom_obj._bom_find(cr, uid, production.product_id.id, production.product_uom.id, properties)
+                bom_id = bom_obj._bom_find(cr, uid, product_id=production.product_id.id, properties=properties, context=context)
                 if bom_id:
                     bom_point = bom_obj.browse(cr, uid, bom_id)
                     routing_id = bom_point.routing_id.id or False
@@ -638,18 +685,17 @@ class mrp_production(osv.osv):
     
             if not bom_id:
                 raise osv.except_osv(_('Error!'), _("Cannot find a bill of material for this product."))
-    
+
             # get components and workcenter_lines from BoM structure
             factor = uom_obj._compute_qty(cr, uid, production.product_uom.id, production.product_qty, bom_point.product_uom.id)
-            res = bom_obj._bom_explode(cr, uid, bom_point, factor / bom_point.product_qty, properties, routing_id=production.routing_id.id)
-            results = res[0] # product_lines
-            results2 = res[1] # workcenter_lines
-    
+            # product_lines, workcenter_lines
+            results, results2 = bom_obj._bom_explode(cr, uid, bom_point, production.product_id, factor / bom_point.product_qty, properties, routing_id=production.routing_id.id, context=context)
+
             # reset product_lines in production order
             for line in results:
                 line['production_id'] = production.id
                 prod_line_obj.create(cr, uid, line)
-    
+
             #reset workcenter_lines in production order
             for line in results2:
                 line['production_id'] = production.id
@@ -671,14 +717,15 @@ class mrp_production(osv.osv):
             context = {}
         move_obj = self.pool.get('stock.move')
         for production in self.browse(cr, uid, ids, context=context):
-            if production.state == 'confirmed' and production.picking_id.state not in ('draft', 'cancel'):
-                raise osv.except_osv(
-                    _('Cannot cancel manufacturing order!'),
-                    _('You must first cancel related internal picking attached to this manufacturing order.'))
             if production.move_created_ids:
                 move_obj.action_cancel(cr, uid, [x.id for x in production.move_created_ids])
             move_obj.action_cancel(cr, uid, [x.id for x in production.move_lines])
         self.write(cr, uid, ids, {'state': 'cancel'})
+        # Put related procurements in exception
+        proc_obj = self.pool.get("procurement.order")
+        procs = proc_obj.search(cr, uid, [('production_id', 'in', ids)], context=context)
+        if procs:
+            proc_obj.write(cr, uid, procs, {'state': 'exception'}, context=context)
         return True
 
     def action_ready(self, cr, uid, ids, context=None):
@@ -690,10 +737,8 @@ class mrp_production(osv.osv):
 
         for production in self.browse(cr, uid, ids, context=context):
             if not production.move_created_ids:
-                produce_move_id = self._make_production_produce_line(cr, uid, production, context=context)
-                for scheduled in production.product_lines:
-                    self._make_production_line_procurement(cr, uid, scheduled, False, context=context)
-        
+                self._make_production_produce_line(cr, uid, production, context=context)
+
             if production.move_prod_id and production.move_prod_id.location_id.id != production.location_dest_id.id:
                 move_obj.write(cr, uid, [production.move_prod_id.id],
                         {'location_id': production.location_dest_id.id})
@@ -706,6 +751,10 @@ class mrp_production(osv.osv):
         for production in self.browse(cr, uid, ids):
             self._costs_generate(cr, uid, production)
         write_res = self.write(cr, uid, ids, {'state': 'done', 'date_finished': time.strftime('%Y-%m-%d %H:%M:%S')})
+        # Check related procurements
+        proc_obj = self.pool.get("procurement.order")
+        procs = proc_obj.search(cr, uid, [('production_id', 'in', ids)], context=context)
+        proc_obj.check(cr, uid, procs, context=context)
         return write_res
 
     def test_production_done(self, cr, uid, ids):
@@ -732,72 +781,127 @@ class mrp_production(osv.osv):
         """
         return 1
 
-    def action_produce(self, cr, uid, production_id, production_qty, production_mode, context=None):
-        """ To produce final product based on production mode (consume/consume&produce).
-        If Production mode is consume, all stock move lines of raw materials will be done/consumed.
-        If Production mode is consume & produce, all stock move lines of raw materials will be done/consumed
-        and stock move lines of final product will be also done/produced.
-        @param production_id: the ID of mrp.production object
-        @param production_qty: specify qty to produce
-        @param production_mode: specify production mode (consume/consume&produce).
-        @return: True
-        """
-        stock_mov_obj = self.pool.get('stock.move')
-        production = self.browse(cr, uid, production_id, context=context)
-
-        if not production.move_lines and production.state == 'ready':
-            # trigger workflow if not products to consume (eg: services)
-            self.signal_button_produce(cr, uid, [production_id])
-
+    def _get_produced_qty(self, cr, uid, production, context=None):
+        ''' returns the produced quantity of product 'production.product_id' for the given production, in the product UoM
+        '''
         produced_qty = 0
         for produced_product in production.move_created_ids2:
             if (produced_product.scrapped) or (produced_product.product_id.id != production.product_id.id):
                 continue
             produced_qty += produced_product.product_qty
-        if production_mode in ['consume','consume_produce']:
-            consumed_data = {}
-
-            # Calculate already consumed qtys
-            for consumed in production.move_lines2:
-                if consumed.scrapped:
-                    continue
-                if not consumed_data.get(consumed.product_id.id, False):
-                    consumed_data[consumed.product_id.id] = 0
-                consumed_data[consumed.product_id.id] += consumed.product_qty
-
-            # Find product qty to be consumed and consume it
-            for scheduled in production.product_lines:
+        return produced_qty
+
+    def _get_consumed_data(self, cr, uid, production, context=None):
+        ''' returns a dictionary containing for each raw material of the given production, its quantity already consumed (in the raw material UoM)
+        '''
+        consumed_data = {}
+        # Calculate already consumed qtys
+        for consumed in production.move_lines2:
+            if consumed.scrapped:
+                continue
+            if not consumed_data.get(consumed.product_id.id, False):
+                consumed_data[consumed.product_id.id] = 0
+            consumed_data[consumed.product_id.id] += consumed.product_qty
+        return consumed_data
 
-                # total qty of consumed product we need after this consumption
-                total_consume = ((production_qty + produced_qty) * scheduled.product_qty / production.product_qty)
+    def _calculate_qty(self, cr, uid, production, product_qty=0.0, context=None):
+        """
+            Calculates the quantity still needed to produce an extra number of products
+            product_qty is in the uom of the product
+        """
+        quant_obj = self.pool.get("stock.quant")
+        uom_obj = self.pool.get("product.uom")
+        produced_qty = self._get_produced_qty(cr, uid, production, context=context)
+        consumed_data = self._get_consumed_data(cr, uid, production, context=context)
+
+        #In case no product_qty is given, take the remaining qty to produce for the given production
+        if not product_qty:
+            product_qty = uom_obj._compute_qty(cr, uid, production.product_uom.id, production.product_qty, production.product_id.uom_id.id) - produced_qty
+        production_qty = uom_obj._compute_qty(cr, uid, production.product_uom.id, production.product_qty, production.product_id.uom_id.id)
+
+        scheduled_qty = {}
+        for scheduled in production.product_lines:
+            if scheduled.product_id.type == 'service':
+                continue
+            qty = uom_obj._compute_qty(cr, uid, scheduled.product_uom.id, scheduled.product_qty, scheduled.product_id.uom_id.id)
+            if scheduled_qty.get(scheduled.product_id.id):
+                scheduled_qty[scheduled.product_id.id] += qty
+            else:
+                scheduled_qty[scheduled.product_id.id] = qty
+        dicts = {}
+        # Find product qty to be consumed and consume it
+        for product_id in scheduled_qty.keys():
+
+            consumed_qty = consumed_data.get(product_id, 0.0)
+            
+            # qty available for consume and produce
+            sched_product_qty = scheduled_qty[product_id]
+            qty_avail = sched_product_qty - consumed_qty
+            if qty_avail <= 0.0:
+                # there will be nothing to consume for this raw material
+                continue
 
-                # qty available for consume and produce
-                qty_avail = scheduled.product_qty - consumed_data.get(scheduled.product_id.id, 0.0)
+            if not dicts.get(product_id):
+                dicts[product_id] = {}
 
-                if float_compare(qty_avail, 0, precision_rounding=scheduled.product_id.uom_id.rounding) <= 0:
-                    # there will be nothing to consume for this raw material
+            # total qty of consumed product we need after this consumption
+            if product_qty + produced_qty <= production_qty:
+                total_consume = ((product_qty + produced_qty) * sched_product_qty / production_qty)
+            else:
+                total_consume = sched_product_qty
+            qty = total_consume - consumed_qty
+
+            # Search for quants related to this related move
+            for move in production.move_lines:
+                if qty <= 0.0:
+                    break
+                if move.product_id.id != product_id:
                     continue
 
-                raw_product = [move for move in production.move_lines if move.product_id.id==scheduled.product_id.id]
-                if raw_product:
-                    # qtys we have to consume
-                    qty = total_consume - consumed_data.get(scheduled.product_id.id, 0.0)
-                    if float_compare(qty, qty_avail, precision_rounding=scheduled.product_id.uom_id.rounding) == 1:
-                        # if qtys we have to consume is more than qtys available to consume
-                        prod_name = scheduled.product_id.name_get()[0][1]
-                        raise osv.except_osv(_('Warning!'), _('You are going to consume total %s quantities of "%s".\nBut you can only consume up to total %s quantities.') % (qty, prod_name, qty_avail))
-                    if float_compare(qty, 0, precision_rounding=scheduled.product_id.uom_id.rounding) <= 0:                        
-                        # we already have more qtys consumed than we need
-                        continue
+                q = min(move.product_qty, qty)
+                quants = quant_obj.quants_get_prefered_domain(cr, uid, move.location_id, move.product_id, q, domain=[('qty', '>', 0.0)],
+                                                     prefered_domain_list=[[('reservation_id', '=', move.id)]], context=context)
+                for quant, quant_qty in quants:
+                    if quant:
+                        lot_id = quant.lot_id.id
+                        if not product_id in dicts.keys():
+                            dicts[product_id] = {lot_id: quant_qty}
+                        elif lot_id in dicts[product_id].keys():
+                            dicts[product_id][lot_id] += quant_qty
+                        else:
+                            dicts[product_id][lot_id] = quant_qty
+                        qty -= quant_qty
+            if qty > 0:
+                if dicts[product_id].get(False):
+                    dicts[product_id][False] += qty
+                else:
+                    dicts[product_id][False] = qty
 
-                    raw_product[0].action_consume(qty, raw_product[0].location_id.id, context=context)
+        consume_lines = []
+        for prod in dicts.keys():
+            for lot, qty in dicts[prod].items():
+                consume_lines.append({'product_id': prod, 'product_qty': qty, 'lot_id': lot})
+        return consume_lines
 
+    def action_produce(self, cr, uid, production_id, production_qty, production_mode, wiz=False, context=None):
+        """ To produce final product based on production mode (consume/consume&produce).
+        If Production mode is consume, all stock move lines of raw materials will be done/consumed.
+        If Production mode is consume & produce, all stock move lines of raw materials will be done/consumed
+        and stock move lines of final product will be also done/produced.
+        @param production_id: the ID of mrp.production object
+        @param production_qty: specify qty to produce in the uom of the production order
+        @param production_mode: specify production mode (consume/consume&produce).
+        @param wiz: the mrp produce product wizard, which will tell the amount of consumed products needed
+        @return: True
+        """
+        stock_mov_obj = self.pool.get('stock.move')
+        uom_obj = self.pool.get("product.uom")
+        production = self.browse(cr, uid, production_id, context=context)
+        production_qty_uom = uom_obj._compute_qty(cr, uid, production.product_uom.id, production_qty, production.product_id.uom_id.id)
+
+        main_production_move = False
         if production_mode == 'consume_produce':
             # To produce remaining qty of final product
-            #vals = {'state':'confirmed'}
-            #final_product_todo = [x.id for x in production.move_created_ids]
-            #stock_mov_obj.write(cr, uid, final_product_todo, vals)
-            #stock_mov_obj.action_confirm(cr, uid, final_product_todo, context)
             produced_products = {}
             for produced_product in production.move_created_ids2:
                 if produced_product.scrapped:
@@ -807,26 +911,55 @@ class mrp_production(osv.osv):
                 produced_products[produced_product.product_id.id] += produced_product.product_qty
 
             for produce_product in production.move_created_ids:
-                produced_qty = produced_products.get(produce_product.product_id.id, 0)
                 subproduct_factor = self._get_subproduct_factor(cr, uid, production.id, produce_product.id, context=context)
-                rest_qty = (subproduct_factor * production.product_qty) - produced_qty
-
-                if rest_qty < (subproduct_factor * production_qty):
-                    prod_name = produce_product.product_id.name_get()[0][1]
-                    raise osv.except_osv(_('Warning!'), _('You are going to produce total %s quantities of "%s".\nBut you can only produce up to total %s quantities.') % ((subproduct_factor * production_qty), prod_name, rest_qty))
-                if rest_qty > 0 :
-                    stock_mov_obj.action_consume(cr, uid, [produce_product.id], (subproduct_factor * production_qty), context=context)
-
-        for raw_product in production.move_lines2:
-            new_parent_ids = []
-            parent_move_ids = [x.id for x in raw_product.move_history_ids]
-            for final_product in production.move_created_ids2:
-                if final_product.id not in parent_move_ids:
-                    new_parent_ids.append(final_product.id)
-            for new_parent_id in new_parent_ids:
-                stock_mov_obj.write(cr, uid, [raw_product.id], {'move_history_ids': [(4,new_parent_id)]})
+                lot_id = False
+                if wiz:
+                    lot_id = wiz.lot_id.id
+                qty = min(subproduct_factor * production_qty_uom, produce_product.product_qty) #Needed when producing more than maximum quantity
+                new_moves = stock_mov_obj.action_consume(cr, uid, [produce_product.id], qty,
+                                                         location_id=produce_product.location_id.id, restrict_lot_id=lot_id, context=context)
+                stock_mov_obj.write(cr, uid, new_moves, {'production_id': production_id}, context=context)
+                remaining_qty = subproduct_factor * production_qty_uom - qty
+                if remaining_qty: # In case you need to make more than planned
+                    #consumed more in wizard than previously planned
+                    extra_move_id = stock_mov_obj.copy(cr, uid, produce_product.id, default={'state': 'confirmed',
+                                                                                             'product_uom_qty': remaining_qty,
+                                                                                             'production_id': production_id}, context=context)
+                    if extra_move_id:
+                        stock_mov_obj.action_done(cr, uid, [extra_move_id], context=context)
+
+                if produce_product.product_id.id == production.product_id.id:
+                    main_production_move = produce_product.id
+
+        if production_mode in ['consume', 'consume_produce']:
+            if wiz:
+                consume_lines = []
+                for cons in wiz.consume_lines:
+                    consume_lines.append({'product_id': cons.product_id.id, 'lot_id': cons.lot_id.id, 'product_qty': cons.product_qty})
+            else:
+                consume_lines = self._calculate_qty(cr, uid, production, production_qty_uom, context=context)
+            for consume in consume_lines:
+                remaining_qty = consume['product_qty']
+                for raw_material_line in production.move_lines:
+                    if remaining_qty <= 0:
+                        break
+                    if consume['product_id'] != raw_material_line.product_id.id:
+                        continue
+                    consumed_qty = min(remaining_qty, raw_material_line.product_qty)
+                    stock_mov_obj.action_consume(cr, uid, [raw_material_line.id], consumed_qty, raw_material_line.location_id.id,
+                                                 restrict_lot_id=consume['lot_id'], consumed_for=main_production_move, context=context)
+                    remaining_qty -= consumed_qty
+                if remaining_qty:
+                    #consumed more in wizard than previously planned
+                    product = self.pool.get('product.product').browse(cr, uid, consume['product_id'], context=context)
+                    extra_move_id = self._make_consume_line_from_data(cr, uid, production, product, product.uom_id.id, remaining_qty, False, 0, context=context)
+                    if extra_move_id:
+                        if consume['lot_id']:
+                            stock_mov_obj.write(cr, uid, [extra_move_id], {'restrict_lot_id': consume['lot_id']}, context=context)
+                        stock_mov_obj.action_done(cr, uid, [extra_move_id], context=context)
+
         self.message_post(cr, uid, production_id, body=_("%s produced") % self._description, context=context)
-        self.signal_button_produce_done(cr, uid, [production_id])
+        self.signal_workflow(cr, uid, [production_id], 'button_produce_done')
         return True
 
     def _costs_generate(self, cr, uid, production):
@@ -857,14 +990,14 @@ class mrp_production(osv.osv):
                         'product_id': wc.product_id.id,
                         'unit_amount': wc_line.hour,
                         'product_uom_id': wc.product_id and wc.product_id.uom_id.id or False
-                    } )
+                    })
                 # Cost per cycle
                 value = wc_line.cycle * wc.costs_cycle
                 account = wc.costs_cycle_account_id.id
                 if value and account:
                     amount += value
                     analytic_line_obj.create(cr, SUPERUSER_ID, {
-                        'name': wc_line.name+' (C)',
+                        'name': wc_line.name + ' (C)',
                         'amount': value,
                         'account_id': account,
                         'general_account_id': wc.costs_general_account_id.id,
@@ -873,7 +1006,7 @@ class mrp_production(osv.osv):
                         'product_id': wc.product_id.id,
                         'unit_amount': wc_line.cycle,
                         'product_uom_id': wc.product_id and wc.product_id.uom_id.id or False
-                    } )
+                    })
         return amount
 
     def action_in_production(self, cr, uid, ids, context=None):
@@ -882,194 +1015,203 @@ class mrp_production(osv.osv):
         """
         return self.write(cr, uid, ids, {'state': 'in_production', 'date_start': time.strftime('%Y-%m-%d %H:%M:%S')})
 
-    def test_if_product(self, cr, uid, ids):
-        """
-        @return: True or False
-        """
+    def consume_lines_get(self, cr, uid, ids, *args):
+        res = []
+        for order in self.browse(cr, uid, ids, context={}):
+            res += [x.id for x in order.move_lines]
+        return res
+
+    def test_ready(self, cr, uid, ids):
         res = True
         for production in self.browse(cr, uid, ids):
-            boms = self._action_compute_lines(cr, uid, [production.id])
-            res = False
-            for bom in boms:
-                product = self.pool.get('product.product').browse(cr, uid, bom['product_id'])
-                if product.type in ('product', 'consu'):
-                    res = True
+            if production.move_lines and not production.ready_production:
+                res = False
         return res
 
-    def _get_auto_picking(self, cr, uid, production):
-        return True
     
-    def _hook_create_post_procurement(self, cr, uid, production, procurement_id, context=None):
-        return True
-
-    def _make_production_line_procurement(self, cr, uid, production_line, shipment_move_id, context=None):
-        procurement_order = self.pool.get('procurement.order')
-        production = production_line.production_id
-        location_id = production.location_src_id.id
-        date_planned = production.date_planned
-        procurement_name = (production.origin or '').split(':')[0] + ':' + production.name
-        procurement_id = procurement_order.create(cr, uid, {
-                    'name': procurement_name,
-                    'origin': procurement_name,
-                    'date_planned': date_planned,
-                    'product_id': production_line.product_id.id,
-                    'product_qty': production_line.product_qty,
-                    'product_uom': production_line.product_uom.id,
-                    'product_uos_qty': production_line.product_uos and production_line.product_qty or False,
-                    'product_uos': production_line.product_uos and production_line.product_uos.id or False,
-                    'location_id': location_id,
-                    'procure_method': production_line.product_id.procure_method,
-                    'move_id': shipment_move_id,
-                    'company_id': production.company_id.id,
-                })
-        procurement_order.signal_button_confirm(cr, uid, [procurement_id])
-        return procurement_id
-
-    def _make_production_internal_shipment_line(self, cr, uid, production_line, shipment_id, parent_move_id, destination_location_id=False, context=None):
-        stock_move = self.pool.get('stock.move')
-        production = production_line.production_id
-        date_planned = production.date_planned
-        # Internal shipment is created for Stockable and Consumer Products
-        if production_line.product_id.type not in ('product', 'consu'):
-            return False
-        source_location_id = production.location_src_id.id
-        if not destination_location_id:
-            destination_location_id = source_location_id
-        return stock_move.create(cr, uid, {
-                        'name': production.name,
-                        'picking_id': shipment_id,
-                        'product_id': production_line.product_id.id,
-                        'product_qty': production_line.product_qty,
-                        'product_uom': production_line.product_uom.id,
-                        'product_uos_qty': production_line.product_uos and production_line.product_uos_qty or False,
-                        'product_uos': production_line.product_uos and production_line.product_uos.id or False,
-                        'date': date_planned,
-                        'move_dest_id': parent_move_id,
-                        'location_id': source_location_id,
-                        'location_dest_id': destination_location_id,
-                        'state': 'waiting',
-                        'company_id': production.company_id.id,
-                })
-
-    def _make_production_internal_shipment(self, cr, uid, production, context=None):
-        ir_sequence = self.pool.get('ir.sequence')
-        stock_picking = self.pool.get('stock.picking')
-        routing_loc = None
-        pick_type = 'internal'
-        partner_id = False
-
-        # Take routing address as a Shipment Address.
-        # If usage of routing location is a internal, make outgoing shipment otherwise internal shipment
-        if production.bom_id.routing_id and production.bom_id.routing_id.location_id:
-            routing_loc = production.bom_id.routing_id.location_id
-            if routing_loc.usage != 'internal':
-                pick_type = 'out'
-            partner_id = routing_loc.partner_id and routing_loc.partner_id.id or False
-
-        # Take next Sequence number of shipment base on type
-        if pick_type!='internal':
-            pick_name = ir_sequence.get(cr, uid, 'stock.picking.' + pick_type)
-        else:
-            pick_name = ir_sequence.get(cr, uid, 'stock.picking')
-
-        picking_id = stock_picking.create(cr, uid, {
-            'name': pick_name,
-            'origin': (production.origin or '').split(':')[0] + ':' + production.name,
-            'type': pick_type,
-            'move_type': 'one',
-            'state': 'auto',
-            'partner_id': partner_id,
-            'auto_picking': self._get_auto_picking(cr, uid, production),
-            'company_id': production.company_id.id,
-        })
-        production.write({'picking_id': picking_id}, context=context)
-        return picking_id
-
+    
     def _make_production_produce_line(self, cr, uid, production, context=None):
         stock_move = self.pool.get('stock.move')
+        proc_obj = self.pool.get('procurement.order')
         source_location_id = production.product_id.property_stock_production.id
         destination_location_id = production.location_dest_id.id
+        procs = proc_obj.search(cr, uid, [('production_id', '=', production.id)], context=context)
+        procurement_id = procs and procs[0] or False
         data = {
             'name': production.name,
             'date': production.date_planned,
             'product_id': production.product_id.id,
-            'product_qty': production.product_qty,
             'product_uom': production.product_uom.id,
+            'product_uom_qty': production.product_qty,
             'product_uos_qty': production.product_uos and production.product_uos_qty or False,
             'product_uos': production.product_uos and production.product_uos.id or False,
             'location_id': source_location_id,
             'location_dest_id': destination_location_id,
             'move_dest_id': production.move_prod_id.id,
-            'state': 'waiting',
+            'procurement_id': procurement_id,
             'company_id': production.company_id.id,
+            'production_id': production.id,
+            'origin': production.name,
         }
         if production.move_prod_id:
             production.move_prod_id.write({'location_id': destination_location_id})
         move_id = stock_move.create(cr, uid, data, context=context)
-        production.write({'move_created_ids': [(6, 0, [move_id])]}, context=context)
-        return move_id
+        #a phantom bom cannot be used in mrp order so it's ok to assume the list returned by action_confirm
+        #is 1 element long, so we can take the first.
+        return stock_move.action_confirm(cr, uid, [move_id], context=context)[0]
 
-    def _make_production_consume_line(self, cr, uid, production_line, parent_move_id, source_location_id=False, context=None):
+    def _get_raw_material_procure_method(self, cr, uid, product, context=None):
+        '''This method returns the procure_method to use when creating the stock move for the production raw materials'''
+        warehouse_obj = self.pool['stock.warehouse']
+        try:
+            mto_route = warehouse_obj._get_mto_route(cr, uid, context=context)
+        except:
+            return "make_to_stock"
+        routes = product.route_ids + product.categ_id.total_route_ids
+        if mto_route in [x.id for x in routes]:
+            return "make_to_order"
+        return "make_to_stock"
+
+    def _create_previous_move(self, cr, uid, move_id, product, source_location_id, dest_location_id, context=None):
+        '''
+        When the routing gives a different location than the raw material location of the production order, 
+        we should create an extra move from the raw material location to the location of the routing, which 
+        precedes the consumption line (chained).  The picking type depends on the warehouse in which this happens
+        and the type of locations. 
+        '''
+        loc_obj = self.pool.get("stock.location")
         stock_move = self.pool.get('stock.move')
-        production = production_line.production_id
+        type_obj = self.pool.get('stock.picking.type')
+        # Need to search for a picking type
+        move = stock_move.browse(cr, uid, move_id, context=context)
+        src_loc = loc_obj.browse(cr, uid, source_location_id, context=context)
+        dest_loc = loc_obj.browse(cr, uid, dest_location_id, context=context)
+        code = stock_move.get_code_from_locs(cr, uid, move, src_loc, dest_loc, context=context)
+        if code == 'outgoing':
+            check_loc = src_loc
+        else:
+            check_loc = dest_loc
+        wh = loc_obj.get_warehouse(cr, uid, check_loc, context=context)
+        domain = [('code', '=', code)]
+        if wh: 
+            domain += [('warehouse_id', '=', wh)]
+        types = type_obj.search(cr, uid, domain, context=context)
+        move = stock_move.copy(cr, uid, move_id, default = {
+            'location_id': source_location_id,
+            'location_dest_id': dest_location_id,
+            'procure_method': self._get_raw_material_procure_method(cr, uid, product, context=context),
+            'raw_material_production_id': False, 
+            'move_dest_id': move_id,
+            'picking_type_id': types and types[0] or False,
+        }, context=context)
+        return move
+
+    def _make_consume_line_from_data(self, cr, uid, production, product, uom_id, qty, uos_id, uos_qty, context=None):
+        stock_move = self.pool.get('stock.move')
+        loc_obj = self.pool.get('stock.location')
         # Internal shipment is created for Stockable and Consumer Products
-        if production_line.product_id.type not in ('product', 'consu'):
+        if product.type not in ('product', 'consu'):
             return False
+        # Take routing location as a Source Location.
+        source_location_id = production.location_src_id.id
+        prod_location_id = source_location_id
+        prev_move= False
+        if production.bom_id.routing_id and production.bom_id.routing_id.location_id and production.bom_id.routing_id.location_id.id != source_location_id:
+            source_location_id = production.bom_id.routing_id.location_id.id
+            prev_move = True
+
         destination_location_id = production.product_id.property_stock_production.id
-        if not source_location_id:
-            source_location_id = production.location_src_id.id
         move_id = stock_move.create(cr, uid, {
             'name': production.name,
             'date': production.date_planned,
-            'product_id': production_line.product_id.id,
-            'product_qty': production_line.product_qty,
-            'product_uom': production_line.product_uom.id,
-            'product_uos_qty': production_line.product_uos and production_line.product_uos_qty or False,
-            'product_uos': production_line.product_uos and production_line.product_uos.id or False,
+            'product_id': product.id,
+            'product_uom_qty': qty,
+            'product_uom': uom_id,
+            'product_uos_qty': uos_id and uos_qty or False,
+            'product_uos': uos_id or False,
             'location_id': source_location_id,
             'location_dest_id': destination_location_id,
-            'move_dest_id': parent_move_id,
-            'state': 'waiting',
             'company_id': production.company_id.id,
-        })
-        production.write({'move_lines': [(4, move_id)]}, context=context)
+            'procure_method': prev_move and 'make_to_stock' or self._get_raw_material_procure_method(cr, uid, product, context=context), #Make_to_stock avoids creating procurement
+            'raw_material_production_id': production.id,
+            #this saves us a browse in create()
+            'price_unit': product.standard_price,
+            'origin': production.name,
+            'warehouse_id': loc_obj.get_warehouse(cr, uid, production.location_src_id, context=context),
+        }, context=context)
+        
+        if prev_move:
+            prev_move = self._create_previous_move(cr, uid, move_id, product, prod_location_id, source_location_id, context=context)
+            stock_move.action_confirm(cr, uid, [prev_move], context=context)
         return move_id
 
+    def _make_production_consume_line(self, cr, uid, line, context=None):
+        return self._make_consume_line_from_data(cr, uid, line.production_id, line.product_id, line.product_uom.id, line.product_qty, line.product_uos.id, line.product_uos_qty, context=context)
+
+
+    def _make_service_procurement(self, cr, uid, line, context=None):
+        prod_obj = self.pool.get('product.product')
+        if prod_obj.need_procurement(cr, uid, [line.product_id.id], context=context):
+            vals = {
+                'name': line.production_id.name,
+                'origin': line.production_id.name,
+                'company_id': line.production_id.company_id.id,
+                'date_planned': line.production_id.date_planned,
+                'product_id': line.product_id.id,
+                'product_qty': line.product_qty,
+                'product_uom': line.product_uom.id,
+                'product_uos_qty': line.product_uos_qty,
+                'product_uos': line.product_uos.id,
+                }
+            proc_obj = self.pool.get("procurement.order")
+            proc = proc_obj.create(cr, uid, vals, context=context)
+            proc_obj.run(cr, uid, [proc], context=context)
+
+
     def action_confirm(self, cr, uid, ids, context=None):
         """ Confirms production order.
         @return: Newly generated Shipment Id.
         """
-        shipment_id = False
-        uncompute_ids = filter(lambda x:x, [not x.product_lines and x.id or False for x in self.browse(cr, uid, ids, context=context)])
+        uncompute_ids = filter(lambda x: x, [not x.product_lines and x.id or False for x in self.browse(cr, uid, ids, context=context)])
         self.action_compute(cr, uid, uncompute_ids, context=context)
         for production in self.browse(cr, uid, ids, context=context):
-            shipment_id = self._make_production_internal_shipment(cr, uid, production, context=context)
-            produce_move_id = self._make_production_produce_line(cr, uid, production, context=context)
-
-            # Take routing location as a Source Location.
-            source_location_id = production.location_src_id.id
-            if production.routing_id and production.routing_id.location_id:
-                source_location_id = production.routing_id.location_id.id
+            self._make_production_produce_line(cr, uid, production, context=context)
 
+            stock_moves = []
             for line in production.product_lines:
-                consume_move_id = self._make_production_consume_line(cr, uid, line, produce_move_id, source_location_id=source_location_id, context=context)
-                if shipment_id:
-                    shipment_move_id = self._make_production_internal_shipment_line(cr, uid, line, shipment_id, consume_move_id,\
-                                 destination_location_id=source_location_id, context=context)
-                    self._make_production_line_procurement(cr, uid, line, shipment_move_id, context=context)
+                if line.product_id.type != 'service':
+                    stock_move_id = self._make_production_consume_line(cr, uid, line, context=context)
+                    stock_moves.append(stock_move_id)
+                else:
+                    self._make_service_procurement(cr, uid, line, context=context)
+            if stock_moves:
+                self.pool.get('stock.move').action_confirm(cr, uid, stock_moves, context=context)
+            production.write({'state': 'confirmed'}, context=context)
+        return 0
+
+    def action_assign(self, cr, uid, ids, context=None):
+        """
+        Checks the availability on the consume lines of the production order
+        """
+        from openerp import workflow
+        move_obj = self.pool.get("stock.move")
+        for production in self.browse(cr, uid, ids, context=context):
+            move_obj.action_assign(cr, uid, [x.id for x in production.move_lines], context=context)
+            if self.pool.get('mrp.production').test_ready(cr, uid, [production.id]):
+                workflow.trg_validate(uid, 'mrp.production', production.id, 'moves_ready', cr)
 
-            if shipment_id:
-                self.pool.get('stock.picking').signal_button_confirm(cr, uid, [shipment_id])
-            production.write({'state':'confirmed'}, context=context)
-        return shipment_id
 
     def force_production(self, cr, uid, ids, *args):
         """ Assigns products.
         @param *args: Arguments
         @return: True
         """
-        pick_obj = self.pool.get('stock.picking')
-        pick_obj.force_assign(cr, uid, [prod.picking_id.id for prod in self.browse(cr, uid, ids)])
+        from openerp import workflow
+        move_obj = self.pool.get('stock.move')
+        for order in self.browse(cr, uid, ids):
+            move_obj.force_assign(cr, uid, [x.id for x in order.move_lines])
+            if self.pool.get('mrp.production').test_ready(cr, uid, [order.id]):
+                workflow.trg_validate(uid, 'mrp.production', order.id, 'moves_ready', cr)
         return True
 
 
@@ -1080,10 +1222,10 @@ class mrp_production_workcenter_line(osv.osv):
     _inherit = ['mail.thread']
 
     _columns = {
-        'name': fields.char('Work Order', size=64, required=True),
+        'name': fields.char('Work Order', required=True),
         'workcenter_id': fields.many2one('mrp.workcenter', 'Work Center', required=True),
-        'cycle': fields.float('Number of Cycles', digits=(16,2)),
-        'hour': fields.float('Number of Hours', digits=(16,2)),
+        'cycle': fields.float('Number of Cycles', digits=(16, 2)),
+        'hour': fields.float('Number of Hours', digits=(16, 2)),
         'sequence': fields.integer('Sequence', required=True, help="Gives the sequence order when displaying a list of work orders."),
         'production_id': fields.many2one('mrp.production', 'Manufacturing Order',
             track_visibility='onchange', select=True, ondelete='cascade', required=True),
@@ -1098,7 +1240,7 @@ class mrp_production_product_line(osv.osv):
     _name = 'mrp.production.product.line'
     _description = 'Production Scheduled Product'
     _columns = {
-        'name': fields.char('Name', size=64, required=True),
+        'name': fields.char('Name', required=True),
         'product_id': fields.many2one('product.product', 'Product', required=True),
         'product_qty': fields.float('Product Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True),
         'product_uom': fields.many2one('product.uom', 'Product Unit of Measure', required=True),
@@ -1107,10 +1249,4 @@ class mrp_production_product_line(osv.osv):
         'production_id': fields.many2one('mrp.production', 'Production Order', select=True),
     }
 
-class product_product(osv.osv):
-    _inherit = "product.product"
-    _columns = {
-        'bom_ids': fields.one2many('mrp.bom', 'product_id', 'Bill of Materials'),
-    }
-
 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: