2009-10-13 05:58:37 +00:00
# -*- coding: utf-8 -*-
2006-12-07 13:41:40 +00:00
##############################################################################
2009-11-13 05:41:16 +00:00
#
2008-11-05 11:45:50 +00:00
# OpenERP, Open Source Management Solution
2010-01-12 09:18:39 +00:00
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
2008-11-03 19:18:56 +00:00
#
# This program is free software: you can redistribute it and/or modify
2009-10-14 11:15:34 +00:00
# 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.
2008-11-03 19:18:56 +00:00
#
# 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
2009-10-14 11:15:34 +00:00
# GNU Affero General Public License for more details.
2008-11-03 19:18:56 +00:00
#
2009-10-14 11:15:34 +00:00
# You should have received a copy of the GNU Affero General Public License
2009-11-13 05:41:16 +00:00
# along with this program. If not, see <http://www.gnu.org/licenses/>.
2006-12-07 13:41:40 +00:00
#
##############################################################################
import time
2012-12-17 15:23:03 +00:00
import openerp . addons . decimal_precision as dp
2015-03-09 11:12:02 +00:00
from collections import OrderedDict
2013-07-31 09:27:32 +00:00
from openerp . osv import fields , osv , orm
2014-12-15 06:53:35 +00:00
from openerp . tools import DEFAULT_SERVER_DATE_FORMAT
2015-04-23 14:40:21 +00:00
from openerp . tools import float_compare , float_is_zero
2012-12-06 14:56:32 +00:00
from openerp . tools . translate import _
2013-05-14 13:26:29 +00:00
from openerp import tools , SUPERUSER_ID
2014-03-06 09:45:04 +00:00
from openerp . addons . product import _common
2006-12-07 13:41:40 +00:00
2013-06-30 22:26:46 +00:00
class mrp_property_group ( osv . osv ) :
"""
Group of mrp properties .
"""
_name = ' mrp.property.group '
_description = ' Property Group '
_columns = {
2014-05-21 09:52:05 +00:00
' name ' : fields . char ( ' Property Group ' , required = True ) ,
2013-06-30 22:26:46 +00:00
' description ' : fields . text ( ' Description ' ) ,
}
class mrp_property ( osv . osv ) :
"""
Properties of mrp .
"""
_name = ' mrp.property '
_description = ' Property '
_columns = {
2014-05-21 09:52:05 +00:00
' name ' : fields . char ( ' Name ' , required = True ) ,
2013-06-30 22:26:46 +00:00
' 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 ' ,
}
2006-12-07 13:41:40 +00:00
#----------------------------------------------------------
2009-12-14 14:56:27 +00:00
# Work Centers
2006-12-07 13:41:40 +00:00
#----------------------------------------------------------
2008-10-10 08:59:13 +00:00
# capacity_hour : capacity per hour. default: 1.0.
2006-12-07 13:41:40 +00:00
# Eg: If 5 concurrent operations at one time: capacity = 5 (because 5 employees)
# unit_per_cycle : how many units are produced for one cycle
2010-08-05 10:20:52 +00:00
2006-12-07 13:41:40 +00:00
class mrp_workcenter ( osv . osv ) :
2008-07-22 15:11:28 +00:00
_name = ' mrp.workcenter '
2009-12-14 14:56:27 +00:00
_description = ' Work Center '
2010-01-12 05:32:48 +00:00
_inherits = { ' resource.resource ' : " resource_id " }
2008-07-22 15:11:28 +00:00
_columns = {
2011-02-18 08:58:56 +00:00
' note ' : fields . text ( ' Description ' , help = " Description of the Work Center. Explain here what ' s a cycle according to this Work Center. " ) ,
' capacity_per_cycle ' : fields . float ( ' Capacity per Cycle ' , help = " Number of operations this Work Center can do in parallel. If this Work Center represents a team of 5 workers, the capacity per cycle is 5. " ) ,
2008-09-22 06:19:37 +00:00
' time_cycle ' : fields . float ( ' Time for 1 cycle (hour) ' , help = " Time in hours for doing one cycle. " ) ,
' time_start ' : fields . float ( ' Time before prod. ' , help = " Time in hours for the setup. " ) ,
' time_stop ' : fields . float ( ' Time after prod. ' , help = " Time in hours for the cleaning. " ) ,
2011-02-18 08:58:56 +00:00
' costs_hour ' : fields . float ( ' Cost per hour ' , help = " Specify Cost of Work Center per hour. " ) ,
2012-09-11 14:51:12 +00:00
' costs_hour_account_id ' : fields . many2one ( ' account.analytic.account ' , ' Hour Account ' , domain = [ ( ' type ' , ' != ' , ' view ' ) ] ,
2012-07-13 10:17:51 +00:00
help = " Fill this only if you want automatic analytic accounting entries on production orders. " ) ,
2011-02-18 08:58:56 +00:00
' costs_cycle ' : fields . float ( ' Cost per cycle ' , help = " Specify Cost of Work Center per cycle. " ) ,
2012-09-11 14:51:12 +00:00
' costs_cycle_account_id ' : fields . many2one ( ' account.analytic.account ' , ' Cycle Account ' , domain = [ ( ' type ' , ' != ' , ' view ' ) ] ,
2012-07-13 10:17:51 +00:00
help = " Fill this only if you want automatic analytic accounting entries on production orders. " ) ,
2008-07-22 15:11:28 +00:00
' costs_journal_id ' : fields . many2one ( ' account.analytic.journal ' , ' Analytic Journal ' ) ,
2012-09-11 14:51:12 +00:00
' costs_general_account_id ' : fields . many2one ( ' account.account ' , ' General Account ' , domain = [ ( ' type ' , ' != ' , ' view ' ) ] ) ,
2010-08-12 19:29:33 +00:00
' resource_id ' : fields . many2one ( ' resource.resource ' , ' Resource ' , ondelete = ' cascade ' , required = True ) ,
2012-07-13 10:17:51 +00:00
' product_id ' : fields . many2one ( ' product.product ' , ' Work Center Product ' , help = " Fill this product to easily track your production costs in the analytic accounting. " ) ,
2008-07-22 15:11:28 +00:00
}
_defaults = {
2010-07-30 12:27:12 +00:00
' capacity_per_cycle ' : 1.0 ,
' resource_type ' : ' material ' ,
2010-01-12 05:32:48 +00:00
}
2010-11-23 13:24:34 +00:00
def on_change_product_cost ( self , cr , uid , ids , product_id , context = None ) :
2010-12-07 10:48:11 +00:00
value = { }
2010-11-23 13:24:34 +00:00
if product_id :
cost = self . pool . get ( ' product.product ' ) . browse ( cr , uid , product_id , context = context )
2010-12-20 14:13:37 +00:00
value = { ' costs_hour ' : cost . standard_price }
2010-12-07 10:48:11 +00:00
return { ' value ' : value }
2010-11-23 13:24:34 +00:00
2015-03-19 10:33:04 +00:00
def _check_capacity_per_cycle ( self , cr , uid , ids , context = None ) :
for obj in self . browse ( cr , uid , ids , context = context ) :
if obj . capacity_per_cycle < = 0.0 :
return False
return True
_constraints = [
( _check_capacity_per_cycle , ' The capacity per cycle must be strictly positive. ' , [ ' capacity_per_cycle ' ] ) ,
]
2006-12-07 13:41:40 +00:00
class mrp_routing ( osv . osv ) :
2010-04-05 12:28:03 +00:00
"""
2011-02-18 08:58:56 +00:00
For specifying the routings of Work Centers .
2010-04-05 12:28:03 +00:00
"""
2008-07-22 15:11:28 +00:00
_name = ' mrp.routing '
_description = ' Routing '
_columns = {
2014-05-21 09:52:05 +00:00
' name ' : fields . char ( ' Name ' , required = True ) ,
2010-11-15 13:15:55 +00:00
' active ' : fields . boolean ( ' Active ' , help = " If the active field is set to False, it will allow you to hide the routing without removing it. " ) ,
2008-07-22 15:11:28 +00:00
' code ' : fields . char ( ' Code ' , size = 8 ) ,
' note ' : fields . text ( ' Description ' ) ,
2014-07-06 14:44:26 +00:00
' workcenter_lines ' : fields . one2many ( ' mrp.routing.workcenter ' , ' routing_id ' , ' Work Centers ' , copy = True ) ,
2008-07-22 15:11:28 +00:00
2008-10-10 08:59:13 +00:00
' location_id ' : fields . many2one ( ' stock.location ' , ' Production Location ' ,
2009-01-27 11:15:46 +00:00
help = " Keep empty if you produce at the location where the finished products are needed. " \
" Set a location if you produce at a fixed location. This can be a partner location " \
2008-09-22 06:19:37 +00:00
" if you subcontract the manufacturing operations. "
) ,
2010-10-15 12:24:55 +00:00
' company_id ' : fields . many2one ( ' res.company ' , ' Company ' ) ,
2008-07-22 15:11:28 +00:00
}
_defaults = {
' active ' : lambda * a : 1 ,
2010-10-15 12:24:55 +00:00
' company_id ' : lambda self , cr , uid , context : self . pool . get ( ' res.company ' ) . _company_default_get ( cr , uid , ' mrp.routing ' , context = context )
2008-07-22 15:11:28 +00:00
}
2006-12-07 13:41:40 +00:00
class mrp_routing_workcenter ( osv . osv ) :
2010-04-05 12:28:03 +00:00
"""
2011-02-18 08:58:56 +00:00
Defines working cycles and hours of a Work Center using routings .
2010-04-05 12:28:03 +00:00
"""
2008-07-22 15:11:28 +00:00
_name = ' mrp.routing.workcenter '
2011-02-18 08:58:56 +00:00
_description = ' Work Center Usage '
2012-05-30 06:37:14 +00:00
_order = ' sequence '
2008-07-22 15:11:28 +00:00
_columns = {
2009-12-14 14:56:27 +00:00
' workcenter_id ' : fields . many2one ( ' mrp.workcenter ' , ' Work Center ' , required = True ) ,
2014-05-21 09:52:05 +00:00
' name ' : fields . char ( ' Name ' , required = True ) ,
2011-02-18 09:53:57 +00:00
' sequence ' : fields . integer ( ' Sequence ' , help = " Gives the sequence order when displaying a list of routing Work Centers. " ) ,
2009-12-21 09:50:54 +00:00
' cycle_nbr ' : fields . float ( ' Number of Cycles ' , required = True ,
2010-12-21 14:35:42 +00:00
help = " Number of iterations this work center has to do in the specified operation of the routing. " ) ,
2011-02-18 09:53:57 +00:00
' hour_nbr ' : fields . float ( ' Number of Hours ' , required = True , help = " Time in hours for this Work Center to achieve the operation of the specified routing. " ) ,
2010-01-12 05:32:48 +00:00
' routing_id ' : fields . many2one ( ' mrp.routing ' , ' Parent Routing ' , select = True , ondelete = ' cascade ' ,
2011-02-18 08:58:56 +00:00
help = " Routing indicates all the Work Centers used, for how long and/or cycles. " \
" If Routing is indicated then,the third tab of a production order (Work Centers) will be automatically pre-completed. " ) ,
2010-10-15 12:24:55 +00:00
' note ' : fields . text ( ' Description ' ) ,
2011-01-06 11:32:21 +00:00
' company_id ' : fields . related ( ' routing_id ' , ' company_id ' , type = ' many2one ' , relation = ' res.company ' , string = ' Company ' , store = True , readonly = True ) ,
2008-07-22 15:11:28 +00:00
}
_defaults = {
' cycle_nbr ' : lambda * a : 1.0 ,
' hour_nbr ' : lambda * a : 0.0 ,
}
2006-12-07 13:41:40 +00:00
class mrp_bom ( osv . osv ) :
2010-04-05 12:28:03 +00:00
"""
Defines bills of material for a product .
"""
2010-06-14 10:28:26 +00:00
_name = ' mrp.bom '
2010-05-19 18:32:32 +00:00
_description = ' Bill of Material '
2012-03-06 13:38:16 +00:00
_inherit = [ ' mail.thread ' ]
2010-06-14 10:28:26 +00:00
2008-07-22 15:11:28 +00:00
_columns = {
2014-05-21 09:52:05 +00:00
' name ' : fields . char ( ' Name ' ) ,
2010-05-12 14:05:51 +00:00
' code ' : fields . char ( ' Reference ' , size = 16 ) ,
2010-11-15 13:15:55 +00:00
' 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. " ) ,
2014-05-27 07:42:52 +00:00
' 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. " ) ,
2014-05-21 09:52:05 +00:00
' position ' : fields . char ( ' Internal Reference ' , help = " Reference to a position in an external plan. " ) ,
2014-08-22 15:28:04 +00:00
' product_tmpl_id ' : fields . many2one ( ' product.template ' , ' Product ' , domain = " [( ' type ' , ' != ' , ' service ' )] " , required = True ) ,
2014-05-27 07:42:52 +00:00
' product_id ' : fields . many2one ( ' product.product ' , ' Product Variant ' ,
2014-08-22 15:28:04 +00:00
domain = " [ ' & ' , ( ' product_tmpl_id ' , ' = ' ,product_tmpl_id), ( ' type ' , ' != ' , ' service ' )] " ,
2014-05-27 07:42:52 +00:00
help = " If a product variant is defined the BOM is available only for this product. " ) ,
2014-07-06 14:44:26 +00:00
' bom_line_ids ' : fields . one2many ( ' mrp.bom.line ' , ' bom_id ' , ' BoM Lines ' , copy = True ) ,
2012-07-14 06:27:55 +00:00
' product_qty ' : fields . float ( ' Product Quantity ' , required = True , digits_compute = dp . get_precision ( ' Product Unit of Measure ' ) ) ,
2012-04-25 08:54:15 +00:00
' 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 " ) ,
2014-05-27 07:42:52 +00:00
' 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. " ) ,
2009-12-23 14:03:29 +00:00
' product_rounding ' : fields . float ( ' Product Rounding ' , help = " Rounding applied on the product quantity. " ) ,
2014-05-27 07:42:52 +00:00
' product_efficiency ' : fields . float ( ' Manufacturing Efficiency ' , required = True , help = " A factor of 0.9 means a loss of 10 % d uring the production process. " ) ,
' property_ids ' : fields . many2many ( ' mrp.property ' , string = ' Properties ' ) ,
2014-02-05 13:25:21 +00:00
' company_id ' : fields . many2one ( ' res.company ' , ' Company ' , required = True ) ,
2008-07-22 15:11:28 +00:00
}
2014-05-27 07:42:52 +00:00
def _get_uom_id ( self , cr , uid , * args ) :
return self . pool [ " product.uom " ] . search ( cr , uid , [ ] , limit = 1 , order = ' id ' ) [ 0 ]
2008-07-22 15:11:28 +00:00
_defaults = {
' active ' : lambda * a : 1 ,
' product_qty ' : lambda * a : 1.0 ,
2014-05-27 07:42:52 +00:00
' product_efficiency ' : lambda * a : 1.0 ,
2010-07-24 11:31:24 +00:00
' product_rounding ' : lambda * a : 0.0 ,
2008-07-22 15:11:28 +00:00
' type ' : lambda * a : ' normal ' ,
2014-05-27 07:42:52 +00:00
' product_uom ' : _get_uom_id ,
2014-02-05 13:25:21 +00:00
' company_id ' : lambda self , cr , uid , c : self . pool . get ( ' res.company ' ) . _company_default_get ( cr , uid , ' mrp.bom ' , context = c ) ,
2008-07-22 15:11:28 +00:00
}
_order = " sequence "
2011-02-14 11:59:43 +00:00
2014-09-26 08:40:55 +00:00
def _bom_find ( self , cr , uid , product_tmpl_id = None , product_id = None , properties = None , context = None ) :
2010-04-05 12:28:03 +00:00
""" Finds BoM for particular product and product uom.
2014-05-27 07:42:52 +00:00
@param product_tmpl_id : Selected product .
2010-04-05 12:28:03 +00:00
@param product_uom : Unit of measure of a product .
@param properties : List of related properties .
@return : False or BoM id .
"""
2015-05-18 15:13:40 +00:00
if not context :
context = { }
2012-03-05 18:40:03 +00:00
if properties is None :
properties = [ ]
2014-05-27 07:42:52 +00:00
if product_id :
2014-08-04 12:22:54 +00:00
if not product_tmpl_id :
2014-09-09 17:06:26 +00:00
product_tmpl_id = self . pool [ ' product.product ' ] . browse ( cr , uid , product_id , context = context ) . product_tmpl_id . id
2014-08-04 12:22:54 +00:00
domain = [
' | ' ,
( ' product_id ' , ' = ' , product_id ) ,
' & ' ,
( ' product_id ' , ' = ' , False ) ,
( ' product_tmpl_id ' , ' = ' , product_tmpl_id )
]
elif product_tmpl_id :
2014-05-27 07:42:52 +00:00
domain = [ ( ' product_id ' , ' = ' , False ) , ( ' product_tmpl_id ' , ' = ' , product_tmpl_id ) ]
2014-08-04 12:22:54 +00:00
else :
# neither product nor template, makes no sense to search
return False
2015-05-18 15:13:40 +00:00
if context . get ( ' company_id ' ) :
domain = domain + [ ( ' company_id ' , ' = ' , context [ ' company_id ' ] ) ]
2015-01-15 13:43:32 +00:00
domain = domain + [ ' | ' , ( ' date_start ' , ' = ' , False ) , ( ' date_start ' , ' <= ' , time . strftime ( DEFAULT_SERVER_DATE_FORMAT ) ) ,
' | ' , ( ' date_stop ' , ' = ' , False ) , ( ' date_stop ' , ' >= ' , time . strftime ( DEFAULT_SERVER_DATE_FORMAT ) ) ]
2014-08-04 12:22:54 +00:00
# order to prioritize bom with product_id over the one without
2015-04-21 12:50:18 +00:00
ids = self . search ( cr , uid , domain , order = ' sequence, product_id ' , context = context )
2014-09-29 09:13:44 +00:00
# Search a BoM which has all properties specified, or if you can not find one, you could
2015-04-21 12:50:18 +00:00
# pass a BoM without any properties with the smallest sequence
2014-09-26 08:40:55 +00:00
bom_empty_prop = False
2014-09-29 09:13:44 +00:00
for bom in self . pool . get ( ' mrp.bom ' ) . browse ( cr , uid , ids , context = context ) :
2014-09-09 17:06:26 +00:00
if not set ( map ( int , bom . property_ids or [ ] ) ) - set ( properties or [ ] ) :
2015-04-21 12:50:18 +00:00
if not properties or bom . property_ids :
2014-09-26 08:40:55 +00:00
return bom . id
2015-04-21 12:50:18 +00:00
elif not bom_empty_prop :
bom_empty_prop = bom . id
2014-09-26 08:40:55 +00:00
return bom_empty_prop
2008-07-22 15:11:28 +00:00
2015-06-15 14:10:50 +00:00
def _skip_bom_line ( self , cr , uid , line , product , context = None ) :
""" Control if a BoM line should be produce, can be inherited for add
custom control .
@param line : BoM line .
@param product : Selected product produced .
@return : True or False
"""
if line . date_start and line . date_start > time . strftime ( DEFAULT_SERVER_DATE_FORMAT ) or \
line . date_stop and line . date_stop < time . strftime ( DEFAULT_SERVER_DATE_FORMAT ) :
return True
# all bom_line_id variant values must be in the product
if line . attribute_value_ids :
if not product or ( set ( map ( int , line . attribute_value_ids or [ ] ) ) - set ( map ( int , product . attribute_value_ids ) ) ) :
return True
return False
2014-09-09 17:06:26 +00:00
def _bom_explode ( self , cr , uid , bom , product , factor , properties = None , level = 0 , routing_id = False , previous_products = None , master_bom = None , context = None ) :
2011-02-18 08:58:56 +00:00
""" Finds Products and Work Centers for related BoM for manufacturing order.
2014-05-27 07:42:52 +00:00
@param bom : BoM of particular product template .
@param product : Select a particular variant of the BoM . If False use BoM without variants .
2014-09-09 17:06:26 +00:00
@param factor : Factor represents the quantity , but in UoM of the BoM , taking into account the numbers produced by the BoM
2010-07-24 09:16:58 +00:00
@param properties : A List of properties Ids .
2010-04-05 12:28:03 +00:00
@param level : Depth level to find BoM lines starts from 10.
2014-05-27 07:42:52 +00:00
@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
2010-04-05 12:28:03 +00:00
@return : result : List of dictionaries containing product details .
2011-02-18 08:58:56 +00:00
result2 : List of dictionaries containing Work Center details .
2010-04-05 12:28:03 +00:00
"""
2014-09-11 14:02:44 +00:00
uom_obj = self . pool . get ( " product.uom " )
2011-07-04 07:00:53 +00:00
routing_obj = self . pool . get ( ' mrp.routing ' )
2014-05-27 07:42:52 +00:00
master_bom = master_bom or bom
2014-09-22 20:17:15 +00:00
2014-05-27 07:42:52 +00:00
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 )
2008-07-22 15:11:28 +00:00
result = [ ]
result2 = [ ]
2014-05-27 07:42:52 +00:00
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 ) ) ,
} )
for bom_line_id in bom . bom_line_ids :
2015-06-15 14:10:50 +00:00
if self . _skip_bom_line ( cr , uid , bom_line_id , product , context = context ) :
continue
2015-04-22 20:02:10 +00:00
if set ( map ( int , bom_line_id . property_ids or [ ] ) ) - set ( properties or [ ] ) :
continue
2014-09-22 20:17:15 +00:00
if previous_products and bom_line_id . product_id . product_tmpl_id . id in previous_products :
2014-05-27 07:42:52 +00:00
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 ] ) )
2014-09-04 15:54:44 +00:00
2014-09-10 17:11:21 +00:00
quantity = _factor ( bom_line_id . product_qty * factor , bom_line_id . product_efficiency , bom_line_id . product_rounding )
2014-09-26 08:40:55 +00:00
bom_id = self . _bom_find ( cr , uid , product_id = bom_line_id . product_id . id , properties = properties , context = context )
2014-09-09 17:06:26 +00:00
#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 " ) :
2014-02-05 13:25:21 +00:00
result . append ( {
2014-05-27 07:42:52 +00:00
' name ' : bom_line_id . product_id . name ,
' product_id ' : bom_line_id . product_id . id ,
2014-09-10 17:11:21 +00:00
' product_qty ' : quantity ,
2014-05-27 07:42:52 +00:00
' product_uom ' : bom_line_id . product_uom . id ,
2014-09-11 14:02:44 +00:00
' 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 ,
2014-05-27 07:42:52 +00:00
' product_uos ' : bom_line_id . product_uos and bom_line_id . product_uos . id or False ,
2008-07-22 15:11:28 +00:00
} )
2014-09-09 17:06:26 +00:00
elif bom_id :
2014-09-22 20:17:15 +00:00
all_prod = [ bom . product_tmpl_id . id ] + ( previous_products or [ ] )
2014-09-09 17:06:26 +00:00
bom2 = self . browse ( cr , uid , bom_id , context = context )
2014-09-11 14:02:44 +00:00
# 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 ,
2014-09-09 17:06:26 +00:00
properties = properties , level = level + 10 , previous_products = all_prod , master_bom = master_bom , context = context )
2014-09-04 15:54:44 +00:00
result = result + res [ 0 ]
result2 = result2 + res [ 1 ]
2014-05-27 07:42:52 +00:00
else :
2014-09-22 20:17:15 +00:00
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 ] ) )
2014-05-27 07:42:52 +00:00
2008-07-22 15:11:28 +00:00
return result , result2
2010-12-13 05:01:46 +00:00
2010-12-06 12:12:20 +00:00
def copy_data ( self , cr , uid , id , default = None , context = None ) :
2010-12-03 13:34:13 +00:00
if default is None :
default = { }
2010-12-06 12:12:20 +00:00
bom_data = self . read ( cr , uid , id , [ ] , context = context )
2014-05-27 07:42:52 +00:00
default . update ( name = _ ( " %s (copy) " ) % ( bom_data [ ' name ' ] ) )
2010-12-06 12:12:20 +00:00
return super ( mrp_bom , self ) . copy_data ( cr , uid , id , default , context = context )
2008-07-22 15:11:28 +00:00
2014-05-27 07:42:52 +00:00
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
2014-11-10 14:36:40 +00:00
def unlink ( self , cr , uid , ids , context = None ) :
2014-11-14 15:58:24 +00:00
if self . pool [ ' mrp.production ' ] . search ( cr , uid , [ ( ' bom_id ' , ' in ' , ids ) , ( ' state ' , ' not in ' , [ ' done ' , ' cancel ' ] ) ] , context = context ) :
2014-11-10 14:36:40 +00:00
raise osv . except_osv ( _ ( ' Warning! ' ) , _ ( ' You can not delete a Bill of Material with running manufacturing orders. \n Please close or cancel it first. ' ) )
return super ( mrp_bom , self ) . unlink ( cr , uid , ids , context = context )
2014-05-27 07:42:52 +00:00
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 "
2015-03-26 08:51:43 +00:00
_rec_name = " product_id "
2014-05-27 07:42:52 +00:00
2014-09-26 14:12:02 +00:00
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
2014-05-27 07:42:52 +00:00
_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. " ) ,
2014-09-26 08:40:55 +00:00
' property_ids ' : fields . many2many ( ' mrp.property ' , string = ' Properties ' ) , #Not used
2014-05-27 07:42:52 +00:00
' bom_id ' : fields . many2one ( ' mrp.bom ' , ' Parent BoM ' , ondelete = ' cascade ' , select = True , required = True ) ,
2014-05-28 17:42:15 +00:00
' attribute_value_ids ' : fields . many2many ( ' product.attribute.value ' , string = ' Variants ' , help = " BOM Product Variants needed form apply this line. " ) ,
2014-09-26 14:12:02 +00:00
' child_line_ids ' : fields . function ( _get_child_bom_lines , relation = " mrp.bom.line " , string = " BOM lines of the referred bom " , type = " one2many " )
2014-05-27 07:42:52 +00:00
}
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 ,
2015-05-05 15:23:46 +00:00
' sequence ' : 1 ,
2014-05-27 07:42:52 +00:00
}
_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 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
2006-12-07 13:41:40 +00:00
class mrp_production ( osv . osv ) :
2010-04-05 12:28:03 +00:00
"""
Production Orders / Manufacturing Orders
"""
2008-07-22 15:11:28 +00:00
_name = ' mrp.production '
2010-05-19 20:57:10 +00:00
_description = ' Manufacturing Order '
2014-02-05 13:25:21 +00:00
_date_name = ' date_planned '
2012-08-22 15:31:45 +00:00
_inherit = [ ' mail.thread ' , ' ir.needaction_mixin ' ]
2008-09-22 06:19:37 +00:00
2010-11-19 13:48:01 +00:00
def _production_calc ( self , cr , uid , ids , prop , unknow_none , context = None ) :
2010-04-05 12:28:03 +00:00
""" Calculates total hours and total no. of cycles for a production order.
2010-04-06 08:57:06 +00:00
@param prop : Name of field .
2010-05-12 14:05:51 +00:00
@param unknow_none :
2010-04-05 12:28:03 +00:00
@return : Dictionary of values .
"""
2008-11-26 10:42:39 +00:00
result = { }
for prod in self . browse ( cr , uid , ids , context = context ) :
result [ prod . id ] = {
' hour_total ' : 0.0 ,
' cycle_total ' : 0.0 ,
}
for wc in prod . workcenter_lines :
result [ prod . id ] [ ' hour_total ' ] + = wc . hour
result [ prod . id ] [ ' cycle_total ' ] + = wc . cycle
return result
[IMP] mrp, stock, stock_account: compute stored fields trigger
This is a performance revision.
Some stored functions field were recomputed uselessly.
In mrp, `hour_total` and `cycle_total` were recomputed
at each write on `mrp.production`, while they should be recomputed
only when there is a change on the `workcenter_lines` field,
or when there is a change in the `hour` or `cycle` field
of these `workcenter_lines`.
In stock, `min_date`, `max_date` and `priority` of
`stock.picking` were recomputed each time a new move
was added to the picking,
wether or not the 'expected_date' of this move
was between the `stock.picking` `min_date` and `max_date`,
and the priority not greater.
In stock, `product_qty` of `stock.move` was recomputed
at each write on the `stock.move`, while it should be
recomputed only when there is a change in `product_id`,
`product_uom` or `product_uom_qty`, as the computation
method only depends on these three fields.
In stock_account, the `invoice_state` of `stock.picking`
was recomputed each time a new `stock.move` was associated
to the picking, wether or not the `invoice_state` of the move
was already the same than the `invoice_state` of the picking.
opw-643560
2015-06-30 10:52:11 +00:00
def _get_workcenter_line ( self , cr , uid , ids , context = None ) :
result = { }
for line in self . pool [ ' mrp.production.workcenter.line ' ] . browse ( cr , uid , ids , context = context ) :
result [ line . production_id . id ] = True
return result . keys ( )
2012-07-24 11:24:57 +00:00
def _src_id_default ( self , cr , uid , ids , context = None ) :
2013-07-31 09:27:32 +00:00
try :
location_model , location_id = self . pool . get ( ' ir.model.data ' ) . get_object_reference ( cr , uid , ' stock ' , ' stock_location_stock ' )
self . pool . get ( ' stock.location ' ) . check_access_rule ( cr , uid , [ location_id ] , ' read ' , context = context )
2014-02-05 13:25:21 +00:00
except ( orm . except_orm , ValueError ) :
2013-07-31 09:27:32 +00:00
location_id = False
return location_id
2012-08-07 11:06:16 +00:00
2012-07-24 11:24:57 +00:00
def _dest_id_default ( self , cr , uid , ids , context = None ) :
2013-07-31 09:27:32 +00:00
try :
location_model , location_id = self . pool . get ( ' ir.model.data ' ) . get_object_reference ( cr , uid , ' stock ' , ' stock_location_stock ' )
self . pool . get ( ' stock.location ' ) . check_access_rule ( cr , uid , [ location_id ] , ' read ' , context = context )
except ( orm . except_orm , ValueError ) :
location_id = False
return location_id
2012-07-24 11:24:57 +00:00
2013-03-05 09:55:33 +00:00
def _get_progress ( self , cr , uid , ids , name , arg , context = None ) :
""" Return product quantity percentage """
result = dict . fromkeys ( ids , 100 )
for mrp_production in self . browse ( cr , uid , ids , context = context ) :
if mrp_production . product_qty :
done = 0.0
for move in mrp_production . move_created_ids2 :
if not move . scrapped and move . product_id == mrp_production . product_id :
done + = move . product_qty
result [ mrp_production . id ] = done / mrp_production . product_qty * 100
2013-01-30 16:00:11 +00:00
return result
2013-08-13 14:23:44 +00:00
def _moves_assigned ( self , cr , uid , ids , name , arg , context = None ) :
""" Test whether all the consume lines are assigned """
2013-09-02 07:28:57 +00:00
res = { }
2013-08-13 14:23:44 +00:00
for production in self . browse ( cr , uid , ids , context = context ) :
2013-09-02 07:28:57 +00:00
res [ production . id ] = True
2013-08-13 14:23:44 +00:00
states = [ x . state != ' assigned ' for x in production . move_lines if x ]
2014-08-26 10:10:05 +00:00
if any ( states ) or len ( states ) == 0 : #When no moves, ready_production will be False, but test_ready will pass
2013-09-02 07:28:57 +00:00
res [ production . id ] = False
2013-08-13 14:23:44 +00:00
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
2008-07-22 15:11:28 +00:00
_columns = {
2014-07-06 14:44:26 +00:00
' name ' : fields . char ( ' Reference ' , required = True , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } , copy = False ) ,
2014-05-21 09:52:05 +00:00
' origin ' : fields . char ( ' Source Document ' , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } ,
2014-07-06 14:44:26 +00:00
help = " Reference of the document that generated this production order request. " , copy = False ) ,
2014-02-05 13:25:21 +00:00
' priority ' : fields . selection ( [ ( ' 0 ' , ' Not urgent ' ) , ( ' 1 ' , ' Normal ' ) , ( ' 2 ' , ' Urgent ' ) , ( ' 3 ' , ' Very Urgent ' ) ] , ' Priority ' ,
2012-09-12 11:30:46 +00:00
select = True , readonly = True , states = dict . fromkeys ( [ ' draft ' , ' confirmed ' ] , [ ( ' readonly ' , False ) ] ) ) ,
2008-07-22 15:11:28 +00:00
2014-08-27 08:15:42 +00:00
' product_id ' : fields . many2one ( ' product.product ' , ' Product ' , required = True , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } ,
domain = [ ( ' type ' , ' != ' , ' service ' ) ] ) ,
2014-02-05 13:25:21 +00:00
' product_qty ' : fields . float ( ' Product Quantity ' , digits_compute = dp . get_precision ( ' Product Unit of Measure ' ) , required = True , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } ) ,
2012-09-12 09:28:26 +00:00
' 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 ) ] } ) ,
2013-03-05 09:55:33 +00:00
' progress ' : fields . function ( _get_progress , type = ' float ' ,
string = ' Production progress ' ) ,
2013-01-30 16:00:11 +00:00
2009-01-27 11:15:46 +00:00
' location_src_id ' : fields . many2one ( ' stock.location ' , ' Raw Materials Location ' , required = True ,
2014-02-05 13:25:21 +00:00
readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } ,
2012-09-12 09:28:26 +00:00
help = " Location where the system will look for components. " ) ,
2009-01-27 11:15:46 +00:00
' location_dest_id ' : fields . many2one ( ' stock.location ' , ' Finished Products Location ' , required = True ,
2014-02-05 13:25:21 +00:00
readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } ,
2012-09-12 09:28:26 +00:00
help = " Location where the system will stock the finished products. " ) ,
2014-07-06 14:44:26 +00:00
' 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 ) ,
2014-05-27 07:42:52 +00:00
' bom_id ' : fields . many2one ( ' mrp.bom ' , ' Bill of Material ' , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } ,
2012-09-24 13:32:44 +00:00
help = " Bill of Materials allow you to define the list of required raw materials to make a finished product. " ) ,
2014-02-05 13:25:21 +00:00
' routing_id ' : fields . many2one ( ' mrp.routing ' , string = ' Routing ' , on_delete = ' set null ' , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } ,
2012-09-12 09:28:26 +00:00
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. " ) ,
2014-07-06 14:44:26 +00:00
' move_prod_id ' : fields . many2one ( ' stock.move ' , ' Product Move ' , readonly = True , copy = False ) ,
2013-09-25 08:37:49 +00:00
' move_lines ' : fields . one2many ( ' stock.move ' , ' raw_material_production_id ' , ' Products to Consume ' ,
2014-02-05 13:25:21 +00:00
domain = [ ( ' state ' , ' not in ' , ( ' done ' , ' cancel ' ) ) ] , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } ) ,
2013-09-25 08:37:49 +00:00
' move_lines2 ' : fields . one2many ( ' stock.move ' , ' raw_material_production_id ' , ' Consumed Products ' ,
2014-02-05 13:25:21 +00:00
domain = [ ( ' state ' , ' in ' , ( ' done ' , ' cancel ' ) ) ] , readonly = True ) ,
2012-09-12 09:28:26 +00:00
' move_created_ids ' : fields . one2many ( ' stock.move ' , ' production_id ' , ' Products to Produce ' ,
2014-02-05 13:25:21 +00:00
domain = [ ( ' state ' , ' not in ' , ( ' done ' , ' cancel ' ) ) ] , readonly = True ) ,
2012-09-12 09:28:26 +00:00
' move_created_ids2 ' : fields . one2many ( ' stock.move ' , ' production_id ' , ' Produced Products ' ,
2014-02-05 13:25:21 +00:00
domain = [ ( ' state ' , ' in ' , ( ' done ' , ' cancel ' ) ) ] , readonly = True ) ,
2012-09-12 09:28:26 +00:00
' product_lines ' : fields . one2many ( ' mrp.production.product.line ' , ' production_id ' , ' Scheduled goods ' ,
2014-01-29 13:10:46 +00:00
readonly = True ) ,
2012-09-12 09:28:26 +00:00
' workcenter_lines ' : fields . one2many ( ' mrp.production.workcenter.line ' , ' production_id ' , ' Work Centers Utilisation ' ,
2014-02-05 13:25:21 +00:00
readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } ) ,
2012-09-12 09:28:26 +00:00
' state ' : fields . selection (
2014-04-23 09:48:07 +00:00
[ ( ' draft ' , ' New ' ) , ( ' cancel ' , ' Cancelled ' ) , ( ' confirmed ' , ' Awaiting Raw Materials ' ) ,
2012-09-12 09:28:26 +00:00
( ' ready ' , ' Ready to Produce ' ) , ( ' in_production ' , ' Production Started ' ) , ( ' done ' , ' Done ' ) ] ,
string = ' Status ' , readonly = True ,
2014-07-06 14:44:26 +00:00
track_visibility = ' onchange ' , copy = False ,
2012-10-12 11:42:58 +00:00
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 \
If the stock is available then the status is set to ' Ready to Produce ' . \n \
When the production gets started then the status is set to ' In Production ' . \n \
When the production is over , the status is set to ' Done ' . " ),
[IMP] mrp, stock, stock_account: compute stored fields trigger
This is a performance revision.
Some stored functions field were recomputed uselessly.
In mrp, `hour_total` and `cycle_total` were recomputed
at each write on `mrp.production`, while they should be recomputed
only when there is a change on the `workcenter_lines` field,
or when there is a change in the `hour` or `cycle` field
of these `workcenter_lines`.
In stock, `min_date`, `max_date` and `priority` of
`stock.picking` were recomputed each time a new move
was added to the picking,
wether or not the 'expected_date' of this move
was between the `stock.picking` `min_date` and `max_date`,
and the priority not greater.
In stock, `product_qty` of `stock.move` was recomputed
at each write on the `stock.move`, while it should be
recomputed only when there is a change in `product_id`,
`product_uom` or `product_uom_qty`, as the computation
method only depends on these three fields.
In stock_account, the `invoice_state` of `stock.picking`
was recomputed each time a new `stock.move` was associated
to the picking, wether or not the `invoice_state` of the move
was already the same than the `invoice_state` of the picking.
opw-643560
2015-06-30 10:52:11 +00:00
' hour_total ' : fields . function ( _production_calc , type = ' float ' , string = ' Total Hours ' , multi = ' workorder ' , store = {
_name : ( lambda self , cr , uid , ids , c = { } : ids , [ ' workcenter_lines ' ] , 40 ) ,
' mrp.production.workcenter.line ' : ( _get_workcenter_line , [ ' hour ' , ' cycle ' ] , 40 ) ,
} ) ,
' cycle_total ' : fields . function ( _production_calc , type = ' float ' , string = ' Total Cycles ' , multi = ' workorder ' , store = {
_name : ( lambda self , cr , uid , ids , c = { } : ids , [ ' workcenter_lines ' ] , 40 ) ,
' mrp.production.workcenter.line ' : ( _get_workcenter_line , [ ' hour ' , ' cycle ' ] , 40 ) ,
} ) ,
2014-02-05 13:25:21 +00:00
' user_id ' : fields . many2one ( ' res.users ' , ' Responsible ' ) ,
' company_id ' : fields . many2one ( ' res.company ' , ' Company ' , required = True ) ,
2013-08-13 14:23:44 +00:00
' ready_production ' : fields . function ( _moves_assigned , type = ' boolean ' , store = { ' stock.move ' : ( _mrp_from_move , [ ' state ' ] , 10 ) } ) ,
2008-07-22 15:11:28 +00:00
}
2014-02-05 13:25:21 +00:00
2008-07-22 15:11:28 +00:00
_defaults = {
' priority ' : lambda * a : ' 1 ' ,
' state ' : lambda * a : ' draft ' ,
2008-09-16 10:37:53 +00:00
' date_planned ' : lambda * a : time . strftime ( ' % Y- % m- %d % H: % M: % S ' ) ,
2014-02-05 13:25:21 +00:00
' product_qty ' : lambda * a : 1.0 ,
2012-10-17 06:00:50 +00:00
' user_id ' : lambda self , cr , uid , c : uid ,
2015-05-05 08:27:50 +00:00
' name ' : lambda self , cr , uid , context : self . pool [ ' ir.sequence ' ] . get ( cr , uid , ' mrp.production ' , context = context ) or ' / ' ,
2010-04-06 08:57:06 +00:00
' company_id ' : lambda self , cr , uid , c : self . pool . get ( ' res.company ' ) . _company_default_get ( cr , uid , ' mrp.production ' , context = c ) ,
2012-07-24 11:24:57 +00:00
' location_src_id ' : _src_id_default ,
' location_dest_id ' : _dest_id_default
2008-07-22 15:11:28 +00:00
}
2014-02-05 13:25:21 +00:00
2011-11-09 06:30:48 +00:00
_sql_constraints = [
2011-11-17 10:37:37 +00:00
( ' name_uniq ' , ' unique(name, company_id) ' , ' Reference must be unique per Company! ' ) ,
2011-11-09 06:30:48 +00:00
]
2014-02-05 13:25:21 +00:00
_order = ' priority desc, date_planned asc '
2010-07-20 05:54:45 +00:00
2010-11-19 13:48:01 +00:00
def _check_qty ( self , cr , uid , ids , context = None ) :
2011-11-02 10:38:06 +00:00
for order in self . browse ( cr , uid , ids , context = context ) :
2010-07-01 13:40:10 +00:00
if order . product_qty < = 0 :
return False
return True
2010-07-20 05:54:45 +00:00
2010-07-01 13:40:10 +00:00
_constraints = [
2011-09-18 13:41:16 +00:00
( _check_qty , ' Order quantity cannot be negative or zero! ' , [ ' product_qty ' ] ) ,
2010-07-01 13:40:10 +00:00
]
2010-07-20 05:54:45 +00:00
2009-01-21 11:21:21 +00:00
def unlink ( self , cr , uid , ids , context = None ) :
2011-12-16 13:52:05 +00:00
for production in self . browse ( cr , uid , ids , context = context ) :
if production . state not in ( ' draft ' , ' cancel ' ) :
2012-08-07 11:06:16 +00:00
raise osv . except_osv ( _ ( ' Invalid Action! ' ) , _ ( ' Cannot delete a manufacturing order in state \' %s \' . ' ) % production . state )
2011-12-16 13:52:05 +00:00
return super ( mrp_production , self ) . unlink ( cr , uid , ids , context = context )
2008-07-22 15:11:28 +00:00
2010-11-19 13:48:01 +00:00
def location_id_change ( self , cr , uid , ids , src , dest , context = None ) :
2010-04-05 12:28:03 +00:00
""" Changes destination location if source location is changed.
@param src : Source location id .
@param dest : Destination location id .
@return : Dictionary of values .
"""
2008-10-04 12:36:34 +00:00
if dest :
return { }
if src :
return { ' value ' : { ' location_dest_id ' : src } }
return { }
2013-08-22 09:01:55 +00:00
def product_id_change ( self , cr , uid , ids , product_id , product_qty = 0 , context = None ) :
2010-04-05 12:28:03 +00:00
""" Finds UoM of changed product.
2010-07-29 08:18:47 +00:00
@param product_id : Id of changed product .
2010-04-05 12:28:03 +00:00
@return : Dictionary of values .
"""
2014-02-11 15:21:16 +00:00
result = { }
2010-07-29 08:18:47 +00:00
if not product_id :
2010-06-23 15:24:51 +00:00
return { ' value ' : {
' product_uom ' : False ,
2010-07-15 10:32:13 +00:00
' bom_id ' : False ,
2013-07-01 11:07:25 +00:00
' routing_id ' : False ,
2014-04-01 13:22:33 +00:00
' product_uos_qty ' : 0 ,
2013-07-01 11:07:25 +00:00
' product_uos ' : False
2010-06-23 15:24:51 +00:00
} }
2010-07-29 08:18:47 +00:00
bom_obj = self . pool . get ( ' mrp.bom ' )
product = self . pool . get ( ' product.product ' ) . browse ( cr , uid , product_id , context = context )
2014-09-26 08:40:55 +00:00
bom_id = bom_obj . _bom_find ( cr , uid , product_id = product . id , properties = [ ] , context = context )
2010-07-29 08:18:47 +00:00
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
2012-07-25 16:15:21 +00:00
product_uom_id = product . uom_id and product . uom_id . id or False
2013-08-22 13:20:36 +00:00
result [ ' value ' ] = { ' product_uos_qty ' : 0 , ' product_uos ' : False , ' product_uom ' : product_uom_id , ' bom_id ' : bom_id , ' routing_id ' : routing_id }
2013-08-22 09:01:55 +00:00
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
2008-07-22 15:11:28 +00:00
2010-07-29 08:18:47 +00:00
def bom_id_change ( self , cr , uid , ids , bom_id , context = None ) :
2010-04-05 12:28:03 +00:00
""" Finds routing for changed BoM.
@param product : Id of product .
@return : Dictionary of values .
"""
2010-07-29 08:18:47 +00:00
if not bom_id :
return { ' value ' : {
' routing_id ' : False
} }
2011-11-02 10:38:06 +00:00
bom_point = self . pool . get ( ' mrp.bom ' ) . browse ( cr , uid , bom_id , context = context )
2010-07-29 08:18:47 +00:00
routing_id = bom_point . routing_id . id or False
result = {
' routing_id ' : routing_id
}
2010-04-06 08:57:06 +00:00
return { ' value ' : result }
2009-08-11 13:32:17 +00:00
2014-04-01 13:22:33 +00:00
[FIX] mrp: prevent creating production lines when testing if production is of product type
The method test_if_product, used in the workflow to test that the mrp production is for a product (!= service), used to call the method _action_compute_lines in order to compute the production lines and determine from them the production type.
The thing is, the method _action_compute_lines, despite the fact it returns the lines of the production, actually creates the lines. So, just to test if the production was of product type, the productin lines were created, in database.
This rev. introduces a _prepare_lines method, which returns the computed production lines, without actually creating them in database, so the test_if_product method can test if the production is of product type without creating the production lines.
Therefore, production lines are now computed and created during the action_compute method, instead of computing them when the production was tested to get the production type.
Computing the lines before the action_compute has as side effect to not set the scheduled date of the work orders in module mrp_operations, at MO confirmation (as, on confirmation, the action_compute method is called only for productions for which the lines are not yet computed, and mrp_operations overide action_compute to set the scheduled date)
opw-620189
2015-01-14 14:22:57 +00:00
def _prepare_lines ( self , cr , uid , production , properties = None , context = None ) :
# search BoM structure and route
bom_obj = self . pool . get ( ' mrp.bom ' )
uom_obj = self . pool . get ( ' product.uom ' )
bom_point = production . bom_id
bom_id = production . bom_id . id
if not bom_point :
2015-01-15 13:43:32 +00:00
bom_id = bom_obj . _bom_find ( cr , uid , product_id = production . product_id . id , properties = properties , context = context )
[FIX] mrp: prevent creating production lines when testing if production is of product type
The method test_if_product, used in the workflow to test that the mrp production is for a product (!= service), used to call the method _action_compute_lines in order to compute the production lines and determine from them the production type.
The thing is, the method _action_compute_lines, despite the fact it returns the lines of the production, actually creates the lines. So, just to test if the production was of product type, the productin lines were created, in database.
This rev. introduces a _prepare_lines method, which returns the computed production lines, without actually creating them in database, so the test_if_product method can test if the production is of product type without creating the production lines.
Therefore, production lines are now computed and created during the action_compute method, instead of computing them when the production was tested to get the production type.
Computing the lines before the action_compute has as side effect to not set the scheduled date of the work orders in module mrp_operations, at MO confirmation (as, on confirmation, the action_compute method is called only for productions for which the lines are not yet computed, and mrp_operations overide action_compute to set the scheduled date)
opw-620189
2015-01-14 14:22:57 +00:00
if bom_id :
bom_point = bom_obj . browse ( cr , uid , bom_id )
routing_id = bom_point . routing_id . id or False
self . write ( cr , uid , [ production . id ] , { ' bom_id ' : bom_id , ' routing_id ' : routing_id } )
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 )
2015-01-15 13:43:32 +00:00
# product_lines, workcenter_lines
return 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 )
[FIX] mrp: prevent creating production lines when testing if production is of product type
The method test_if_product, used in the workflow to test that the mrp production is for a product (!= service), used to call the method _action_compute_lines in order to compute the production lines and determine from them the production type.
The thing is, the method _action_compute_lines, despite the fact it returns the lines of the production, actually creates the lines. So, just to test if the production was of product type, the productin lines were created, in database.
This rev. introduces a _prepare_lines method, which returns the computed production lines, without actually creating them in database, so the test_if_product method can test if the production is of product type without creating the production lines.
Therefore, production lines are now computed and created during the action_compute method, instead of computing them when the production was tested to get the production type.
Computing the lines before the action_compute has as side effect to not set the scheduled date of the work orders in module mrp_operations, at MO confirmation (as, on confirmation, the action_compute method is called only for productions for which the lines are not yet computed, and mrp_operations overide action_compute to set the scheduled date)
opw-620189
2015-01-14 14:22:57 +00:00
2013-09-12 13:14:34 +00:00
def _action_compute_lines ( self , cr , uid , ids , properties = None , context = None ) :
""" Compute product_lines and workcenter_lines from BoM structure
2013-09-12 13:54:45 +00:00
@return : product_lines
2010-04-05 12:28:03 +00:00
"""
2012-10-02 11:12:31 +00:00
if properties is None :
properties = [ ]
2008-07-22 15:11:28 +00:00
results = [ ]
2010-03-22 11:28:29 +00:00
prod_line_obj = self . pool . get ( ' mrp.production.product.line ' )
workcenter_line_obj = self . pool . get ( ' mrp.production.workcenter.line ' )
2013-09-12 13:14:34 +00:00
for production in self . browse ( cr , uid , ids , context = context ) :
#unlink product_lines
2013-09-16 11:33:23 +00:00
prod_line_obj . unlink ( cr , SUPERUSER_ID , [ line . id for line in production . product_lines ] , context = context )
2013-09-12 13:14:34 +00:00
#unlink workcenter_lines
2013-09-16 11:33:23 +00:00
workcenter_line_obj . unlink ( cr , SUPERUSER_ID , [ line . id for line in production . workcenter_lines ] , context = context )
[FIX] mrp: prevent creating production lines when testing if production is of product type
The method test_if_product, used in the workflow to test that the mrp production is for a product (!= service), used to call the method _action_compute_lines in order to compute the production lines and determine from them the production type.
The thing is, the method _action_compute_lines, despite the fact it returns the lines of the production, actually creates the lines. So, just to test if the production was of product type, the productin lines were created, in database.
This rev. introduces a _prepare_lines method, which returns the computed production lines, without actually creating them in database, so the test_if_product method can test if the production is of product type without creating the production lines.
Therefore, production lines are now computed and created during the action_compute method, instead of computing them when the production was tested to get the production type.
Computing the lines before the action_compute has as side effect to not set the scheduled date of the work orders in module mrp_operations, at MO confirmation (as, on confirmation, the action_compute method is called only for productions for which the lines are not yet computed, and mrp_operations overide action_compute to set the scheduled date)
opw-620189
2015-01-14 14:22:57 +00:00
res = self . _prepare_lines ( cr , uid , production , properties = properties , context = context )
2013-09-12 13:14:34 +00:00
results = res [ 0 ] # product_lines
results2 = res [ 1 ] # workcenter_lines
2014-09-22 15:42:59 +00:00
2013-09-12 13:14:34 +00:00
# reset product_lines in production order
2008-07-22 15:11:28 +00:00
for line in results :
line [ ' production_id ' ] = production . id
2010-03-22 11:28:29 +00:00
prod_line_obj . create ( cr , uid , line )
2014-02-05 13:25:21 +00:00
2013-09-12 13:14:34 +00:00
#reset workcenter_lines in production order
2008-07-22 15:11:28 +00:00
for line in results2 :
line [ ' production_id ' ] = production . id
2015-04-20 06:39:07 +00:00
workcenter_line_obj . create ( cr , uid , line , context )
2013-09-12 13:54:45 +00:00
return results
2008-07-22 15:11:28 +00:00
2013-09-12 11:55:56 +00:00
def action_compute ( self , cr , uid , ids , properties = None , context = None ) :
""" Computes bills of material of a product.
@param properties : List containing dictionaries of properties .
@return : No . of products .
"""
2013-09-12 13:54:45 +00:00
return len ( self . _action_compute_lines ( cr , uid , ids , properties = properties , context = context ) )
2008-07-22 15:11:28 +00:00
2011-01-07 09:11:58 +00:00
def action_cancel ( self , cr , uid , ids , context = None ) :
2010-04-05 12:28:03 +00:00
""" Cancels the production order and related stock moves.
@return : True
"""
2011-01-07 09:11:58 +00:00
if context is None :
context = { }
2010-03-22 11:28:29 +00:00
move_obj = self . pool . get ( ' stock.move ' )
2014-11-19 11:33:08 +00:00
proc_obj = self . pool . get ( ' procurement.order ' )
2011-01-07 09:11:58 +00:00
for production in self . browse ( cr , uid , ids , context = context ) :
2008-07-22 15:11:28 +00:00
if production . move_created_ids :
2010-03-22 11:28:29 +00:00
move_obj . action_cancel ( cr , uid , [ x . id for x in production . move_created_ids ] )
2014-11-19 11:33:08 +00:00
procs = proc_obj . search ( cr , uid , [ ( ' move_dest_id ' , ' in ' , [ x . id for x in production . move_lines ] ) ] , context = context )
if procs :
proc_obj . cancel ( cr , uid , procs , context = context )
2010-03-22 11:28:29 +00:00
move_obj . action_cancel ( cr , uid , [ x . id for x in production . move_lines ] )
2010-08-05 10:20:52 +00:00
self . write ( cr , uid , ids , { ' state ' : ' cancel ' } )
2014-04-23 14:17:00 +00:00
# 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 :
2015-04-01 11:16:06 +00:00
proc_obj . message_post ( cr , uid , procs , body = _ ( ' Manufacturing order cancelled. ' ) , context = context )
2014-04-23 14:17:00 +00:00
proc_obj . write ( cr , uid , procs , { ' state ' : ' exception ' } , context = context )
2008-07-22 15:11:28 +00:00
return True
2012-03-06 13:38:16 +00:00
def action_ready ( self , cr , uid , ids , context = None ) :
2010-04-05 12:28:03 +00:00
""" Changes the production state to Ready and location id of stock move.
@return : True
"""
2010-03-22 11:28:29 +00:00
move_obj = self . pool . get ( ' stock.move ' )
2010-04-06 08:57:06 +00:00
self . write ( cr , uid , ids , { ' state ' : ' ready ' } )
2010-07-01 13:40:10 +00:00
2013-09-11 14:11:23 +00:00
for production in self . browse ( cr , uid , ids , context = context ) :
if not production . move_created_ids :
2014-02-05 13:25:21 +00:00
self . _make_production_produce_line ( cr , uid , production , context = context )
2012-04-16 08:47:14 +00:00
if production . move_prod_id and production . move_prod_id . location_id . id != production . location_dest_id . id :
2010-03-22 11:28:29 +00:00
move_obj . write ( cr , uid , [ production . move_prod_id . id ] ,
2010-04-06 08:57:06 +00:00
{ ' location_id ' : production . location_dest_id . id } )
2008-07-22 15:11:28 +00:00
return True
2012-03-06 13:38:16 +00:00
def action_production_end ( self , cr , uid , ids , context = None ) :
2010-04-05 12:28:03 +00:00
""" Changes production state to Finish and writes finished date.
@return : True
"""
2008-07-22 15:11:28 +00:00
for production in self . browse ( cr , uid , ids ) :
self . _costs_generate ( cr , uid , production )
2012-03-22 09:01:54 +00:00
write_res = self . write ( cr , uid , ids , { ' state ' : ' done ' , ' date_finished ' : time . strftime ( ' % Y- % m- %d % H: % M: % S ' ) } )
2014-04-23 14:17:00 +00:00
# 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 )
2012-03-22 09:01:54 +00:00
return write_res
2010-02-24 08:17:06 +00:00
def test_production_done ( self , cr , uid , ids ) :
2010-04-05 12:28:03 +00:00
""" Tests whether production is done or not.
@return : True or False
"""
2010-02-24 08:17:06 +00:00
res = True
2010-05-12 14:05:51 +00:00
for production in self . browse ( cr , uid , ids ) :
if production . move_lines :
2011-04-29 08:49:48 +00:00
res = False
2010-02-24 08:17:06 +00:00
2010-05-12 14:05:51 +00:00
if production . move_created_ids :
2011-04-29 08:49:48 +00:00
res = False
2010-02-24 08:17:06 +00:00
return res
2011-11-09 10:01:06 +00:00
2011-11-09 15:10:38 +00:00
def _get_subproduct_factor ( self , cr , uid , production_id , move_id = None , context = None ) :
2012-03-30 08:11:36 +00:00
""" Compute the factor to compute the qty of procucts to produce for the given production_id. By default,
it ' s always equal to the quantity encoded in the production order or the production wizard, but if the
module mrp_subproduct is installed , then we must use the move_id to identify the product to produce
2011-11-09 15:10:38 +00:00
and its quantity .
: param production_id : ID of the mrp . order
: param move_id : ID of the stock move that needs to be produced . Will be used in mrp_subproduct .
: return : The factor to apply to the quantity that we should produce for the given production order .
2011-11-09 10:01:06 +00:00
"""
2011-11-09 15:10:38 +00:00
return 1
2010-02-24 08:17:06 +00:00
2014-01-21 10:40:51 +00:00
def _get_produced_qty ( self , cr , uid , production , context = None ) :
2014-02-05 10:43:24 +00:00
''' returns the produced quantity of product ' production.product_id ' for the given production, in the product UoM
'''
2014-01-21 10:40:51 +00:00
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
return produced_qty
def _get_consumed_data ( self , cr , uid , production , context = None ) :
2014-02-05 10:43:24 +00:00
''' returns a dictionary containing for each raw material of the given production, its quantity already consumed (in the raw material UoM)
'''
2014-01-21 10:40:51 +00:00
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
2014-02-05 10:43:24 +00:00
def _calculate_qty ( self , cr , uid , production , product_qty = 0.0 , context = None ) :
2014-01-21 13:39:09 +00:00
"""
Calculates the quantity still needed to produce an extra number of products
2014-09-22 20:17:15 +00:00
product_qty is in the uom of the product
2014-01-21 13:39:09 +00:00
"""
quant_obj = self . pool . get ( " stock.quant " )
2014-09-22 15:42:59 +00:00
uom_obj = self . pool . get ( " product.uom " )
2014-01-21 13:39:09 +00:00
produced_qty = self . _get_produced_qty ( cr , uid , production , context = context )
consumed_data = self . _get_consumed_data ( cr , uid , production , context = context )
2014-02-05 10:43:24 +00:00
#In case no product_qty is given, take the remaining qty to produce for the given production
if not product_qty :
2014-09-29 15:48:38 +00:00
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 )
2014-02-05 10:43:24 +00:00
2015-03-09 11:12:02 +00:00
scheduled_qty = OrderedDict ( )
2014-01-21 13:39:09 +00:00
for scheduled in production . product_lines :
2014-08-26 10:10:05 +00:00
if scheduled . product_id . type == ' service ' :
continue
2014-09-22 20:17:15 +00:00
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
2015-03-09 11:12:02 +00:00
dicts = OrderedDict ( )
2014-09-22 20:17:15 +00:00
# Find product qty to be consumed and consume it
for product_id in scheduled_qty . keys ( ) :
2014-05-27 07:42:52 +00:00
consumed_qty = consumed_data . get ( product_id , 0.0 )
2014-02-05 10:43:24 +00:00
# qty available for consume and produce
2014-09-22 20:17:15 +00:00
sched_product_qty = scheduled_qty [ product_id ]
qty_avail = sched_product_qty - consumed_qty
2014-02-05 10:43:24 +00:00
if qty_avail < = 0.0 :
# there will be nothing to consume for this raw material
continue
2014-05-27 07:42:52 +00:00
if not dicts . get ( product_id ) :
dicts [ product_id ] = { }
2014-02-05 10:43:24 +00:00
2014-01-21 13:39:09 +00:00
# total qty of consumed product we need after this consumption
2014-09-29 15:48:38 +00:00
if product_qty + produced_qty < = production_qty :
total_consume = ( ( product_qty + produced_qty ) * sched_product_qty / production_qty )
2014-08-14 20:12:45 +00:00
else :
2014-09-22 20:17:15 +00:00
total_consume = sched_product_qty
2014-02-05 10:43:24 +00:00
qty = total_consume - consumed_qty
2014-01-21 13:39:09 +00:00
# Search for quants related to this related move
2014-02-05 10:43:24 +00:00
for move in production . move_lines :
if qty < = 0.0 :
break
2014-05-27 07:42:52 +00:00
if move . product_id . id != product_id :
2014-02-05 10:43:24 +00:00
continue
q = min ( move . product_qty , qty )
2014-09-22 20:17:15 +00:00
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 )
2014-02-05 13:25:21 +00:00
for quant , quant_qty in quants :
2014-02-05 10:43:24 +00:00
if quant :
lot_id = quant . lot_id . id
2014-01-21 13:39:09 +00:00
if not product_id in dicts . keys ( ) :
2014-02-05 13:25:21 +00:00
dicts [ product_id ] = { lot_id : quant_qty }
2014-01-21 13:39:09 +00:00
elif lot_id in dicts [ product_id ] . keys ( ) :
2014-02-05 10:43:24 +00:00
dicts [ product_id ] [ lot_id ] + = quant_qty
2014-01-21 13:39:09 +00:00
else :
2014-02-05 10:43:24 +00:00
dicts [ product_id ] [ lot_id ] = quant_qty
qty - = quant_qty
[FIX] mrp: always use float_compare to compare floats
When comparing a float value to 0.0,
it can happen the float value is very near to 0.0,
but not exactly 0. This is the point to use float_compare,
```float_compare(qty, 0, self.pool['decimal.precision'].precision_get(cr, uid, 'Product Unit of Measure'))´´´
will compare qty to 0, with the product unit of measure precision (0.01 by default).
So, if qty is equal to 0.00001, this means the qty is regarded as equal to 0.0.
(float_compare will return 1).
2015-03-09 10:46:36 +00:00
if float_compare ( qty , 0 , self . pool [ ' decimal.precision ' ] . precision_get ( cr , uid , ' Product Unit of Measure ' ) ) == 1 :
2014-02-05 10:43:24 +00:00
if dicts [ product_id ] . get ( False ) :
dicts [ product_id ] [ False ] + = qty
else :
dicts [ product_id ] [ False ] = qty
2014-02-05 13:25:21 +00:00
2014-01-21 13:39:09 +00:00
consume_lines = [ ]
for prod in dicts . keys ( ) :
2014-02-05 10:43:24 +00:00
for lot , qty in dicts [ prod ] . items ( ) :
consume_lines . append ( { ' product_id ' : prod , ' product_qty ' : qty , ' lot_id ' : lot } )
2014-01-21 13:39:09 +00:00
return consume_lines
def action_produce ( self , cr , uid , production_id , production_qty , production_mode , wiz = False , context = None ) :
2010-07-28 10:11:25 +00:00
""" To produce final product based on production mode (consume/consume&produce).
2010-04-05 12:28:03 +00:00
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
2014-09-22 20:17:15 +00:00
@param production_qty : specify qty to produce in the uom of the production order
2010-04-05 12:28:03 +00:00
@param production_mode : specify production mode ( consume / consume & produce ) .
2014-02-05 10:43:24 +00:00
@param wiz : the mrp produce product wizard , which will tell the amount of consumed products needed
2010-04-05 12:28:03 +00:00
@return : True
2010-05-12 14:05:51 +00:00
"""
2010-02-24 08:17:06 +00:00
stock_mov_obj = self . pool . get ( ' stock.move ' )
2014-09-22 20:17:15 +00:00
uom_obj = self . pool . get ( " product.uom " )
2010-11-19 13:48:01 +00:00
production = self . browse ( cr , uid , production_id , context = context )
2014-09-22 20:17:15 +00:00
production_qty_uom = uom_obj . _compute_qty ( cr , uid , production . product_uom . id , production_qty , production . product_id . uom_id . id )
2015-04-23 14:40:21 +00:00
precision = self . pool [ ' decimal.precision ' ] . precision_get ( cr , uid , ' Product Unit of Measure ' )
2014-02-05 13:25:21 +00:00
2014-01-21 09:30:27 +00:00
main_production_move = False
2014-01-16 17:48:00 +00:00
if production_mode == ' consume_produce ' :
# To produce remaining qty of final product
produced_products = { }
for produced_product in production . move_created_ids2 :
if produced_product . scrapped :
continue
if not produced_products . get ( produced_product . product_id . id , False ) :
produced_products [ produced_product . product_id . id ] = 0
produced_products [ produced_product . product_id . id ] + = produced_product . product_qty
for produce_product in production . move_created_ids :
subproduct_factor = self . _get_subproduct_factor ( cr , uid , production . id , produce_product . id , context = context )
2014-08-14 20:12:45 +00:00
lot_id = False
if wiz :
lot_id = wiz . lot_id . id
2014-10-25 19:30:18 +00:00
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 ,
2014-09-22 20:17:15 +00:00
location_id = produce_product . location_id . id , restrict_lot_id = lot_id , context = context )
2014-08-14 20:12:45 +00:00
stock_mov_obj . write ( cr , uid , new_moves , { ' production_id ' : production_id } , context = context )
2014-10-25 19:30:18 +00:00
remaining_qty = subproduct_factor * production_qty_uom - qty
2015-06-18 09:29:38 +00:00
if not float_is_zero ( remaining_qty , precision_digits = precision ) :
2015-04-23 14:40:21 +00:00
# In case you need to make more than planned
2014-10-25 19:30:18 +00:00
#consumed more in wizard than previously planned
2015-03-10 16:13:16 +00:00
extra_move_id = stock_mov_obj . copy ( cr , uid , produce_product . id , default = { ' product_uom_qty ' : remaining_qty ,
2014-10-25 19:30:18 +00:00
' production_id ' : production_id } , context = context )
2015-03-10 16:13:16 +00:00
stock_mov_obj . action_confirm ( cr , uid , [ extra_move_id ] , context = context )
stock_mov_obj . action_done ( cr , uid , [ extra_move_id ] , context = context )
2014-10-25 19:30:18 +00:00
2014-10-17 14:16:28 +00:00
if produce_product . product_id . id == production . product_id . id :
main_production_move = produce_product . id
2014-01-21 09:30:27 +00:00
2014-02-05 13:25:21 +00:00
if production_mode in [ ' consume ' , ' consume_produce ' ] :
2014-01-21 13:39:09 +00:00
if wiz :
2014-02-05 10:43:24 +00:00
consume_lines = [ ]
2014-01-21 13:39:09 +00:00
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 :
2014-09-22 20:17:15 +00:00
consume_lines = self . _calculate_qty ( cr , uid , production , production_qty_uom , context = context )
2014-01-21 13:39:09 +00:00
for consume in consume_lines :
2014-02-05 10:43:24 +00:00
remaining_qty = consume [ ' product_qty ' ]
for raw_material_line in production . move_lines :
2015-03-10 16:13:16 +00:00
if raw_material_line . state in ( ' done ' , ' cancel ' ) :
continue
2014-02-05 10:43:24 +00:00
if remaining_qty < = 0 :
break
2014-02-05 13:25:21 +00:00
if consume [ ' product_id ' ] != raw_material_line . product_id . id :
2014-02-05 10:43:24 +00:00
continue
consumed_qty = min ( remaining_qty , raw_material_line . product_qty )
2014-10-25 19:30:18 +00:00
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 )
2014-02-05 10:43:24 +00:00
remaining_qty - = consumed_qty
2015-06-18 09:29:38 +00:00
if not float_is_zero ( remaining_qty , precision_digits = precision ) :
2014-04-23 15:05:03 +00:00
#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 )
2015-03-10 16:13:16 +00:00
stock_mov_obj . write ( cr , uid , [ extra_move_id ] , { ' restrict_lot_id ' : consume [ ' lot_id ' ] ,
' consumed_for ' : main_production_move } , context = context )
stock_mov_obj . action_done ( cr , uid , [ extra_move_id ] , context = context )
2011-01-07 09:11:58 +00:00
2014-01-16 17:48:00 +00:00
self . message_post ( cr , uid , production_id , body = _ ( " %s produced " ) % self . _description , context = context )
2015-03-17 15:21:33 +00:00
# Remove remaining products to consume if no more products to produce
if not production . move_created_ids and production . move_lines :
stock_mov_obj . action_cancel ( cr , uid , [ x . id for x in production . move_lines ] , context = context )
2014-07-07 09:50:30 +00:00
self . signal_workflow ( cr , uid , [ production_id ] , ' button_produce_done ' )
2014-01-16 17:48:00 +00:00
return True
2011-11-11 11:48:07 +00:00
2008-07-22 15:11:28 +00:00
def _costs_generate ( self , cr , uid , production ) :
2010-04-05 12:28:03 +00:00
""" Calculates total costs at the end of the production.
@param production : Id of production order .
@return : Calculated amount .
"""
2008-07-22 15:11:28 +00:00
amount = 0.0
2010-03-22 11:28:29 +00:00
analytic_line_obj = self . pool . get ( ' account.analytic.line ' )
2008-07-22 15:11:28 +00:00
for wc_line in production . workcenter_lines :
wc = wc_line . workcenter_id
if wc . costs_journal_id and wc . costs_general_account_id :
2012-06-01 14:32:49 +00:00
# Cost per hour
2008-07-22 15:11:28 +00:00
value = wc_line . hour * wc . costs_hour
account = wc . costs_hour_account_id . id
if value and account :
amount + = value
2013-05-14 13:26:29 +00:00
# we user SUPERUSER_ID as we do not garantee an mrp user
# has access to account analytic lines but still should be
# able to produce orders
analytic_line_obj . create ( cr , SUPERUSER_ID , {
2010-04-06 08:57:06 +00:00
' name ' : wc_line . name + ' (H) ' ,
2008-07-22 15:11:28 +00:00
' amount ' : value ,
' account_id ' : account ,
' general_account_id ' : wc . costs_general_account_id . id ,
' journal_id ' : wc . costs_journal_id . id ,
2010-11-23 13:24:34 +00:00
' ref ' : wc . code ,
' product_id ' : wc . product_id . id ,
2010-12-07 17:51:16 +00:00
' unit_amount ' : wc_line . hour ,
2012-06-01 11:35:03 +00:00
' product_uom_id ' : wc . product_id and wc . product_id . uom_id . id or False
2014-02-05 13:25:21 +00:00
} )
2012-06-01 14:32:49 +00:00
# Cost per cycle
2008-07-22 15:11:28 +00:00
value = wc_line . cycle * wc . costs_cycle
account = wc . costs_cycle_account_id . id
if value and account :
amount + = value
2013-05-14 13:26:29 +00:00
analytic_line_obj . create ( cr , SUPERUSER_ID , {
2014-02-05 13:25:21 +00:00
' name ' : wc_line . name + ' (C) ' ,
2008-07-22 15:11:28 +00:00
' amount ' : value ,
' account_id ' : account ,
' general_account_id ' : wc . costs_general_account_id . id ,
' journal_id ' : wc . costs_journal_id . id ,
2010-11-23 13:24:34 +00:00
' ref ' : wc . code ,
' product_id ' : wc . product_id . id ,
2010-12-07 17:51:16 +00:00
' unit_amount ' : wc_line . cycle ,
2012-06-01 11:35:03 +00:00
' product_uom_id ' : wc . product_id and wc . product_id . uom_id . id or False
2014-02-05 13:25:21 +00:00
} )
2008-07-22 15:11:28 +00:00
return amount
2012-03-06 13:38:16 +00:00
def action_in_production ( self , cr , uid , ids , context = None ) :
2010-04-05 12:28:03 +00:00
""" Changes state to In Production and writes starting date.
2010-05-12 14:05:51 +00:00
@return : True
2010-04-05 12:28:03 +00:00
"""
2012-12-18 16:27:48 +00:00
return self . write ( cr , uid , ids , { ' state ' : ' in_production ' , ' date_start ' : time . strftime ( ' % Y- % m- %d % H: % M: % S ' ) } )
2008-07-22 15:11:28 +00:00
2013-09-18 13:56:12 +00:00
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
2014-02-05 13:25:21 +00:00
2013-09-18 13:56:12 +00:00
def test_ready ( self , cr , uid , ids ) :
2014-08-26 10:10:05 +00:00
res = True
2008-07-22 15:11:28 +00:00
for production in self . browse ( cr , uid , ids ) :
2014-08-26 10:10:05 +00:00
if production . move_lines and not production . ready_production :
res = False
2008-07-22 15:11:28 +00:00
return res
2014-08-26 10:10:05 +00:00
2014-04-23 09:52:58 +00:00
2011-12-08 13:08:24 +00:00
def _make_production_produce_line ( self , cr , uid , production , context = None ) :
stock_move = self . pool . get ( ' stock.move ' )
2014-10-16 08:21:07 +00:00
proc_obj = self . pool . get ( ' procurement.order ' )
2012-07-27 01:52:02 +00:00
source_location_id = production . product_id . property_stock_production . id
2011-12-08 13:08:24 +00:00
destination_location_id = production . location_dest_id . id
2014-10-16 08:21:07 +00:00
procs = proc_obj . search ( cr , uid , [ ( ' production_id ' , ' = ' , production . id ) ] , context = context )
2014-12-03 14:12:01 +00:00
procurement = procs and \
proc_obj . browse ( cr , uid , procs [ 0 ] , context = context ) or False
2011-12-08 13:08:24 +00:00
data = {
2012-09-28 09:50:35 +00:00
' name ' : production . name ,
2011-12-08 13:08:24 +00:00
' date ' : production . date_planned ,
' product_id ' : production . product_id . id ,
' product_uom ' : production . product_uom . id ,
2014-02-05 13:25:21 +00:00
' product_uom_qty ' : production . product_qty ,
2011-12-08 13:08:24 +00:00
' 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 ,
2014-12-03 14:12:01 +00:00
' procurement_id ' : procurement and procurement . id ,
2011-12-08 13:08:24 +00:00
' company_id ' : production . company_id . id ,
2014-02-14 08:46:50 +00:00
' production_id ' : production . id ,
2014-04-16 15:18:25 +00:00
' origin ' : production . name ,
2014-12-03 14:12:01 +00:00
' group_id ' : procurement and procurement . group_id . id ,
2011-12-08 13:08:24 +00:00
}
move_id = stock_move . create ( cr , uid , data , context = context )
2014-02-14 08:46:50 +00:00
#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 ]
2011-12-08 13:08:24 +00:00
2015-03-12 09:46:19 +00:00
def _get_raw_material_procure_method ( self , cr , uid , product , location_id = False , location_dest_id = False , context = None ) :
''' This method returns the procure_method to use when creating the stock move for the production raw materials
Besides the standard configuration of looking if the product or product category has the MTO route ,
you can also define a rule e . g . from Stock to Production ( which might be used in the future like the sale orders )
'''
2014-09-22 12:20:45 +00:00
warehouse_obj = self . pool [ ' stock.warehouse ' ]
2015-03-12 09:46:19 +00:00
routes = product . route_ids + product . categ_id . total_route_ids
if location_id and location_dest_id :
pull_obj = self . pool [ ' procurement.rule ' ]
pulls = pull_obj . search ( cr , uid , [ ( ' route_id ' , ' in ' , [ x . id for x in routes ] ) ,
( ' location_id ' , ' = ' , location_dest_id ) ,
( ' location_src_id ' , ' = ' , location_id ) ] , limit = 1 , context = context )
if pulls :
return pull_obj . browse ( cr , uid , pulls [ 0 ] , context = context ) . procure_method
2014-04-01 12:51:04 +00:00
try :
2014-09-22 12:20:45 +00:00
mto_route = warehouse_obj . _get_mto_route ( cr , uid , context = context )
2014-04-01 12:51:04 +00:00
except :
return " make_to_stock "
2015-03-12 09:46:19 +00:00
2014-04-01 13:32:40 +00:00
if mto_route in [ x . id for x in routes ] :
return " make_to_order "
return " make_to_stock "
2014-04-01 12:51:04 +00:00
2014-07-16 09:58:22 +00:00
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
2014-09-08 13:20:01 +00:00
precedes the consumption line ( chained ) . The picking type depends on the warehouse in which this happens
and the type of locations .
2014-07-16 09:58:22 +00:00
'''
2014-09-08 13:20:01 +00:00
loc_obj = self . pool . get ( " stock.location " )
2014-06-19 14:58:52 +00:00
stock_move = self . pool . get ( ' stock.move ' )
2014-09-01 13:39:32 +00:00
type_obj = self . pool . get ( ' stock.picking.type ' )
2014-09-08 13:20:01 +00:00
# Need to search for a picking type
2014-09-09 17:06:26 +00:00
move = stock_move . browse ( cr , uid , move_id , context = context )
2014-09-18 13:50:41 +00:00
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 )
2014-09-09 17:06:26 +00:00
if code == ' outgoing ' :
2014-09-18 13:50:41 +00:00
check_loc = src_loc
2014-09-09 17:06:26 +00:00
else :
2014-09-18 13:50:41 +00:00
check_loc = dest_loc
2014-09-08 13:20:01 +00:00
wh = loc_obj . get_warehouse ( cr , uid , check_loc , context = context )
2014-09-09 17:06:26 +00:00
domain = [ ( ' code ' , ' = ' , code ) ]
2014-09-08 13:20:01 +00:00
if wh :
domain + = [ ( ' warehouse_id ' , ' = ' , wh ) ]
types = type_obj . search ( cr , uid , domain , context = context )
2014-07-16 09:58:22 +00:00
move = stock_move . copy ( cr , uid , move_id , default = {
2014-06-19 14:58:52 +00:00
' location_id ' : source_location_id ,
' location_dest_id ' : dest_location_id ,
2015-03-12 09:46:19 +00:00
' procure_method ' : self . _get_raw_material_procure_method ( cr , uid , product , location_id = source_location_id ,
location_dest_id = dest_location_id , context = context ) ,
2014-07-16 09:58:22 +00:00
' raw_material_production_id ' : False ,
' move_dest_id ' : move_id ,
2014-09-01 13:39:32 +00:00
' picking_type_id ' : types and types [ 0 ] or False ,
2014-07-16 09:58:22 +00:00
} , context = context )
return move
2014-06-19 14:58:52 +00:00
2014-04-23 15:05:03 +00:00
def _make_consume_line_from_data ( self , cr , uid , production , product , uom_id , qty , uos_id , uos_qty , context = None ) :
2011-12-08 13:08:24 +00:00
stock_move = self . pool . get ( ' stock.move ' )
2014-09-17 12:32:06 +00:00
loc_obj = self . pool . get ( ' stock.location ' )
2011-12-08 13:08:24 +00:00
# Internal shipment is created for Stockable and Consumer Products
2014-04-23 15:05:03 +00:00
if product . type not in ( ' product ' , ' consu ' ) :
2011-12-08 13:08:24 +00:00
return False
2014-04-23 15:05:03 +00:00
# Take routing location as a Source Location.
source_location_id = production . location_src_id . id
2014-07-16 09:58:22 +00:00
prod_location_id = source_location_id
2014-06-19 14:58:52 +00:00
prev_move = False
2014-06-25 16:17:32 +00:00
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 :
2014-04-23 15:05:03 +00:00
source_location_id = production . bom_id . routing_id . location_id . id
2014-07-16 09:58:22 +00:00
prev_move = True
2014-09-17 12:32:06 +00:00
2012-07-27 01:52:02 +00:00
destination_location_id = production . product_id . property_stock_production . id
2011-12-09 11:21:31 +00:00
move_id = stock_move . create ( cr , uid , {
2012-09-28 09:50:35 +00:00
' name ' : production . name ,
2011-12-08 13:08:24 +00:00
' date ' : production . date_planned ,
2014-04-23 15:05:03 +00:00
' 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 ,
2011-12-08 13:08:24 +00:00
' location_id ' : source_location_id ,
' location_dest_id ' : destination_location_id ,
' company_id ' : production . company_id . id ,
2015-03-12 09:46:19 +00:00
' procure_method ' : prev_move and ' make_to_stock ' or self . _get_raw_material_procure_method ( cr , uid , product , location_id = source_location_id ,
location_dest_id = destination_location_id , context = context ) , #Make_to_stock avoids creating procurement
2014-02-14 08:46:50 +00:00
' raw_material_production_id ' : production . id ,
2014-03-25 15:21:52 +00:00
#this saves us a browse in create()
2014-04-23 15:05:03 +00:00
' price_unit ' : product . standard_price ,
2014-04-16 15:18:25 +00:00
' origin ' : production . name ,
2014-09-17 12:32:06 +00:00
' warehouse_id ' : loc_obj . get_warehouse ( cr , uid , production . location_src_id , context = context ) ,
2014-12-03 14:12:01 +00:00
' group_id ' : production . move_prod_id . group_id . id ,
2014-07-16 09:58:22 +00:00
} , context = context )
2014-06-19 14:58:52 +00:00
if prev_move :
2014-07-16 09:58:22 +00:00
prev_move = self . _create_previous_move ( cr , uid , move_id , product , prod_location_id , source_location_id , context = context )
2014-09-01 13:39:32 +00:00
stock_move . action_confirm ( cr , uid , [ prev_move ] , context = context )
2014-03-25 15:21:52 +00:00
return move_id
2011-12-08 13:08:24 +00:00
2014-04-23 15:05:03 +00:00
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 )
2014-08-08 15:22:14 +00:00
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 )
2011-12-08 13:08:24 +00:00
def action_confirm ( self , cr , uid , ids , context = None ) :
2010-04-05 12:28:03 +00:00
""" Confirms production order.
2011-12-08 13:08:24 +00:00
@return : Newly generated Shipment Id .
2010-04-05 12:28:03 +00:00
"""
2015-04-20 06:39:07 +00:00
user_lang = self . pool . get ( ' res.users ' ) . browse ( cr , uid , [ uid ] ) . partner_id . lang
context = dict ( context , lang = user_lang )
2014-02-05 13:25:21 +00:00
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 ) ] )
2011-12-08 13:08:24 +00:00
self . action_compute ( cr , uid , uncompute_ids , context = context )
for production in self . browse ( cr , uid , ids , context = context ) :
2014-04-23 15:05:03 +00:00
self . _make_production_produce_line ( cr , uid , production , context = context )
2014-02-05 13:25:21 +00:00
2014-03-25 15:21:52 +00:00
stock_moves = [ ]
2008-07-22 15:11:28 +00:00
for line in production . product_lines :
2014-08-08 15:22:14 +00:00
if line . product_id . type != ' service ' :
stock_move_id = self . _make_production_consume_line ( cr , uid , line , context = context )
2014-08-26 15:28:40 +00:00
stock_moves . append ( stock_move_id )
2014-08-08 15:22:14 +00:00
else :
self . _make_service_procurement ( cr , uid , line , context = context )
2014-04-23 15:05:03 +00:00
if stock_moves :
self . pool . get ( ' stock.move ' ) . action_confirm ( cr , uid , stock_moves , context = context )
2014-11-13 11:30:26 +00:00
production . write ( { ' state ' : ' confirmed ' } )
2013-07-12 16:54:49 +00:00
return 0
2008-07-22 15:11:28 +00:00
2014-04-22 08:58:31 +00:00
def action_assign ( self , cr , uid , ids , context = None ) :
"""
Checks the availability on the consume lines of the production order
"""
2014-08-26 10:10:05 +00:00
from openerp import workflow
2014-04-22 08:58:31 +00:00
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 )
2014-08-26 10:10:05 +00:00
if self . pool . get ( ' mrp.production ' ) . test_ready ( cr , uid , [ production . id ] ) :
workflow . trg_validate ( uid , ' mrp.production ' , production . id , ' moves_ready ' , cr )
2014-04-22 08:58:31 +00:00
2008-07-22 15:11:28 +00:00
def force_production ( self , cr , uid , ids , * args ) :
2010-04-05 12:28:03 +00:00
""" Assigns products.
@param * args : Arguments
@return : True
"""
2014-08-26 10:10:05 +00:00
from openerp import workflow
2013-09-18 13:56:12 +00:00
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 ] )
2014-08-26 10:10:05 +00:00
if self . pool . get ( ' mrp.production ' ) . test_ready ( cr , uid , [ order . id ] ) :
workflow . trg_validate ( uid , ' mrp.production ' , order . id , ' moves_ready ' , cr )
2008-07-22 15:11:28 +00:00
return True
2012-08-07 11:06:16 +00:00
2006-12-07 13:41:40 +00:00
class mrp_production_workcenter_line ( osv . osv ) :
2008-07-22 15:11:28 +00:00
_name = ' mrp.production.workcenter.line '
2010-05-19 18:32:32 +00:00
_description = ' Work Order '
2009-05-31 09:22:26 +00:00
_order = ' sequence '
2012-03-06 13:38:16 +00:00
_inherit = [ ' mail.thread ' ]
2010-05-12 14:05:51 +00:00
2008-07-22 15:11:28 +00:00
_columns = {
2014-05-21 09:52:05 +00:00
' name ' : fields . char ( ' Work Order ' , required = True ) ,
2009-12-14 14:56:27 +00:00
' workcenter_id ' : fields . many2one ( ' mrp.workcenter ' , ' Work Center ' , required = True ) ,
2014-02-05 13:25:21 +00:00
' cycle ' : fields . float ( ' Number of Cycles ' , digits = ( 16 , 2 ) ) ,
' hour ' : fields . float ( ' Number of Hours ' , digits = ( 16 , 2 ) ) ,
2009-12-21 13:14:12 +00:00
' sequence ' : fields . integer ( ' Sequence ' , required = True , help = " Gives the sequence order when displaying a list of work orders. " ) ,
2012-12-18 17:11:46 +00:00
' production_id ' : fields . many2one ( ' mrp.production ' , ' Manufacturing Order ' ,
2012-12-20 11:47:30 +00:00
track_visibility = ' onchange ' , select = True , ondelete = ' cascade ' , required = True ) ,
2008-07-22 15:11:28 +00:00
}
_defaults = {
' sequence ' : lambda * a : 1 ,
' hour ' : lambda * a : 0 ,
' cycle ' : lambda * a : 0 ,
}
2006-12-07 13:41:40 +00:00
class mrp_production_product_line ( osv . osv ) :
2008-07-22 15:11:28 +00:00
_name = ' mrp.production.product.line '
2010-05-19 18:32:32 +00:00
_description = ' Production Scheduled Product '
2008-07-22 15:11:28 +00:00
_columns = {
2014-05-21 09:52:05 +00:00
' name ' : fields . char ( ' Name ' , required = True ) ,
2008-07-22 15:11:28 +00:00
' product_id ' : fields . many2one ( ' product.product ' , ' Product ' , required = True ) ,
2012-06-13 11:26:07 +00:00
' product_qty ' : fields . float ( ' Product Quantity ' , digits_compute = dp . get_precision ( ' Product Unit of Measure ' ) , required = True ) ,
2012-04-25 08:54:15 +00:00
' product_uom ' : fields . many2one ( ' product.uom ' , ' Product Unit of Measure ' , required = True ) ,
2012-06-22 12:03:53 +00:00
' product_uos_qty ' : fields . float ( ' Product UOS Quantity ' ) ,
2008-07-22 15:11:28 +00:00
' product_uos ' : fields . many2one ( ' product.uom ' , ' Product UOS ' ) ,
' production_id ' : fields . many2one ( ' mrp.production ' , ' Production Order ' , select = True ) ,
}
2006-12-07 13:41:40 +00:00
2010-08-05 10:20:52 +00:00
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: