-# -*- encoding: utf-8 -*-
+# -*- coding: utf-8 -*-
##############################################################################
#
-# Copyright (c) 2004-2008 TINY SPRL. (http://tiny.be) All Rights Reserved.
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
#
-# $Id$
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
#
-# WARNING: This program as such is intended to be used by professional
-# programmers who take the whole responsability of assessing all potential
-# consequences resulting from its eventual inadequacies and bugs
-# End users who are looking for a ready-to-use solution with commercial
-# garantees and support are strongly adviced to contract a Free Software
-# Service Company
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
#
-# This program is Free Software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import netsvc
import time
from mx import DateTime
+from tools.translate import _
#----------------------------------------------------------
# Workcenters
#----------------------------------------------------------
-# capacity_hour : capacity per hour. default: 1.0.
+# capacity_hour : capacity per hour. default: 1.0.
# Eg: If 5 concurrent operations at one time: capacity = 5 (because 5 employees)
# unit_per_cycle : how many units are produced for one cycle
#
'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."),
- 'time_efficiency': fields.float('Time Efficiency', help="Factor that multiplies all times expressed in the workcenter."),
+ 'time_efficiency': fields.float('Time Efficiency', help="Factor to adjust the work center cycle and before and after production durations"),
'costs_hour': fields.float('Cost per hour'),
'costs_hour_account_id': fields.many2one('account.analytic.account', 'Hour Account', domain=[('type','<>','view')],
'note': fields.text('Description'),
'workcenter_lines': fields.one2many('mrp.routing.workcenter', 'routing_id', 'Workcenters'),
- 'location_id': fields.many2one('stock.location', 'Production Location',
- help="Keep empty if you produce at the location where the finnished products are needed." \
- "Put a location if you produce at a fixed location. This can be a partner location " \
+ 'location_id': fields.many2one('stock.location', 'Production Location',
+ 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 " \
"if you subcontract the manufacturing operations."
),
}
'workcenter_id': fields.many2one('mrp.workcenter', 'Workcenter', required=True),
'name': fields.char('Name', size=64, required=True),
'sequence': fields.integer('Sequence'),
- 'cycle_nbr': fields.float('Number of Cycle', required=True),
- 'hour_nbr': fields.float('Number of Hours', required=True),
- 'routing_id': fields.many2one('mrp.routing', 'Parent Routing', select=True),
+ 'cycle_nbr': fields.float('Number of Cycle', required=True,
+ help="Time in hours for doing one cycle."),
+ 'hour_nbr': fields.float('Number of Hours', required=True, help="Cost per hour"),
+ 'routing_id': fields.many2one('mrp.routing', 'Parent Routing', select=True,
+ help="routing indicates all the workcenters used, for how long and/or cycles." \
+ "If Routing is indicated then,the third tab of a production order (workcenters) will be automatically pre-completed."),
'note': fields.text('Description')
}
_defaults = {
class mrp_bom(osv.osv):
_name = 'mrp.bom'
_description = 'Bill of Material'
+ def _child_compute(self, cr, uid, ids, name, arg, context={}):
+ result = {}
+ for bom in self.browse(cr, uid, ids, context=context):
+ result[bom.id] = map(lambda x: x.id, bom.bom_lines)
+ if bom.bom_lines:
+ continue
+ ok = ((name=='child_complete_ids') and (bom.product_id.supply_method=='produce'))
+ if bom.type=='phantom' or ok:
+ sids = self.pool.get('mrp.bom').search(cr, uid, [('bom_id','=',False),('product_id','=',bom.product_id.id)])
+ if sids:
+ bom2 = self.pool.get('mrp.bom').browse(cr, uid, sids[0], context=context)
+ result[bom.id] += map(lambda x: x.id, bom2.bom_lines)
+ return result
+ def _compute_type(self, cr, uid, ids, field_name, arg, context):
+ res = dict(map(lambda x: (x,''), ids))
+ for line in self.browse(cr, uid, ids):
+ if line.type=='phantom' and not line.bom_id:
+ res[line.id] = 'set'
+ continue
+ if line.bom_lines or line.type=='phantom':
+ continue
+ if line.product_id.supply_method=='produce':
+ if line.product_id.procure_method=='make_to_stock':
+ res[line.id] = 'stock'
+ else:
+ res[line.id] = 'order'
+ return res
_columns = {
'name': fields.char('Name', size=64, required=True),
'code': fields.char('Code', size=16),
'active': fields.boolean('Active'),
- 'type': fields.selection([('normal','Normal BoM'),('phantom','Sets / Phantom')], 'BoM Type', required=True, help="Use a phantom bill of material in lines that have a sub-bom and that have to be automatically computed in one line, without having two production orders."),
+ 'type': fields.selection([('normal','Normal BoM'),('phantom','Sets / Phantom')], 'BoM Type', required=True, help=
+ "Use a phantom bill of material in raw materials lines that have to be " \
+ "automatically computed in on eproduction order and not one per level." \
+ "If you put \"Phantom/Set\" at the root level of a bill of material " \
+ "it is considered as a set or pack: the products are replaced by the components " \
+ "between the sale order to the picking without going through the production order." \
+ "The normal BoM will generate one production order per BoM level."),
+ 'method': fields.function(_compute_type, string='Method', method=True, type='selection', selection=[('',''),('stock','On Stock'),('order','On Order'),('set','Set / Pack')]),
'date_start': fields.date('Valid From', help="Validity of this BoM or component. Keep empty if it's always valid."),
'date_stop': fields.date('Valid Until', help="Validity of this BoM or component. Keep empty if it's always valid."),
'sequence': fields.integer('Sequence'),
'position': fields.char('Internal Ref.', size=64, help="Reference to a position in an external plan."),
'product_id': fields.many2one('product.product', 'Product', required=True),
'product_uos_qty': fields.float('Product UOS Qty'),
- 'product_uos': fields.many2one('product.uom', 'Product UOS'),
+ '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 Qty', required=True),
- 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True),
- 'product_rounding': fields.float('Product Rounding'),
- 'product_efficiency': fields.float('Product Efficiency', required=True),
+ 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True, help="UoM (Unit of Measure) is the unit of measurement for the inventory control"),
+ 'product_rounding': fields.float('Product Rounding', help="Rounding applied on the product quantity. For integer only values, put 1.0"),
+ 'product_efficiency': fields.float('Product Efficiency', required=True, help="Material efficiency. A factor of 0.9 means a loss of 10% in the production."),
'bom_lines': fields.one2many('mrp.bom', 'bom_id', 'BoM Lines'),
'bom_id': fields.many2one('mrp.bom', 'Parent BoM', ondelete='cascade', select=True),
- 'routing_id': fields.many2one('mrp.routing', 'Routing', help="The list of operations (list of workcenters) to produce the finnished product. The routing is mainly used to compute workcenter costs during operations and to plan futur loads on workcenters based on production plannification."),
+ 'routing_id': fields.many2one('mrp.routing', 'Routing', help="The list of operations (list of workcenters) to produce the finished product. The routing is mainly used to compute workcenter costs during operations and to plan futur loads on workcenters based on production plannification."),
'property_ids': fields.many2many('mrp.property', 'mrp_bom_property_rel', 'bom_id','property_id', 'Properties'),
'revision_ids': fields.one2many('mrp.bom.revision', 'bom_id', 'BoM Revisions'),
- 'revision_type': fields.selection([('numeric','numeric indices'),('alpha','alphabetical indices')], 'indice type')
+ 'revision_type': fields.selection([('numeric','numeric indices'),('alpha','alphabetical indices')], 'Index type'),
+ 'child_ids': fields.function(_child_compute,relation='mrp.bom', method=True, string="BoM Hierarchy", type='many2many'),
+ 'child_complete_ids': fields.function(_child_compute,relation='mrp.bom', method=True, string="BoM Hierarchy", type='many2many')
}
_defaults = {
'active': lambda *a: 1,
}
_order = "sequence"
_sql_constraints = [
- ('bom_qty_zero', 'CHECK (product_qty>0)', 'All product quantities must be greater than 0 !'),
+ ('bom_qty_zero', 'CHECK (product_qty>0)', 'All product quantities must be greater than 0.\n' \
+ 'You should install the mrp_subproduct module if you want to manage extra products on BoMs !'),
]
+
def _check_recursion(self, cr, uid, ids):
- level = 500
+ level = 100
while len(ids):
- cr.execute('select distinct bom_id from mrp_bom where id in ('+','.join(map(str,ids))+')')
+ cr.execute('select distinct bom_id from mrp_bom where id =ANY(%s)',(ids,))
ids = filter(None, map(lambda x:x[0], cr.fetchall()))
if not level:
return False
(_check_recursion, 'Error ! You can not create recursive BoM.', ['parent_id'])
]
-
+
def onchange_product_id(self, cr, uid, ids, product_id, name, context={}):
if product_id:
prod=self.pool.get('product.product').browse(cr,uid,[product_id])[0]
return {'value': v}
return {}
- def _bom_find(self, cr, uid, product_id, product_uom, properties = []):
+ def _bom_find(self, cr, uid, product_id, product_uom, properties=[]):
bom_result = False
# Why searching on BoM without parent ?
- cr.execute('select id from mrp_bom where product_id=%d and bom_id is null order by sequence', (product_id,))
+ cr.execute('select id from mrp_bom where product_id=%s and bom_id is null order by sequence', (product_id,))
ids = map(lambda x: x[0], cr.fetchall())
max_prop = 0
result = False
prop+=1
if (prop>max_prop) or ((max_prop==0) and not result):
result = bom.id
+ max_prop = prop
return result
- def _bom_explode(self, cr, uid, bom, factor, properties, addthis=False, level=10):
+ def _bom_explode(self, cr, uid, bom, factor, properties, addthis=False, level=0):
factor = factor / (bom.product_efficiency or 1.0)
factor = rounding(factor, bom.product_rounding)
if factor<bom.product_rounding:
factor = bom.product_rounding
result = []
result2 = []
+ phantom=False
if bom.type=='phantom' and not bom.bom_lines:
newbom = self._bom_find(cr, uid, bom.product_id.id, bom.product_uom.id, properties)
if newbom:
res = self._bom_explode(cr, uid, self.browse(cr, uid, [newbom])[0], factor*bom.product_qty, properties, addthis=True, level=level+10)
result = result + res[0]
result2 = result2 + res[1]
+ phantom=True
else:
- return [],[]
- else:
+ phantom=False
+ if not phantom:
if addthis and not bom.bom_lines:
result.append(
{
for wc_use in bom.routing_id.workcenter_lines:
wc = wc_use.workcenter_id
d, m = divmod(factor, wc_use.workcenter_id.capacity_per_cycle)
- cycle = (d + (m and 1.0 or 0.0)) * wc_use.cycle_nbr
+ mult = (d + (m and 1.0 or 0.0))
+ cycle = mult * wc_use.cycle_nbr
result2.append({
'name': bom.routing_id.name,
'workcenter_id': wc.id,
- 'sequence': level,
+ 'sequence': level+(wc_use.sequence or 0),
'cycle': cycle,
- 'hour': wc_use.hour_nbr + (wc.time_start+wc.time_stop+cycle*wc.time_cycle) * (wc.time_efficiency or 1.0),
+ 'hour': float(wc_use.hour_nbr*mult + (wc.time_start+wc.time_stop+cycle*wc.time_cycle) * (wc.time_efficiency or 1.0)),
})
for bom2 in bom.bom_lines:
res = self._bom_explode(cr, uid, bom2, factor, properties, addthis=True, level=level+10)
def set_indices(self, cr, uid, ids, context = {}):
if not ids or (ids and not ids[0]):
- return True
+ return True
res = self.read(cr, uid, ids, ['revision_ids', 'revision_type'])
rev_ids = res[0]['revision_ids']
idx = 1
'author_id': fields.many2one('res.users', 'Author'),
'bom_id': fields.many2one('mrp.bom', 'BoM', select=True),
}
-
+
_defaults = {
'author_id': lambda x,y,z,c: z,
'date': lambda *a: time.strftime('%Y-%m-%d'),
productions=self.read(cr,uid,ids,['id','move_prod_id'])
res={}
for production in productions:
+ res[production['id']]=False
if production.get('move_prod_id',False):
parent_move_line=get_parent_move(production['move_prod_id'][0])
if parent_move_line:
move = move_obj.browse(cr,uid,parent_move_line)
+ #TODO: fix me sale module can not be used here,
+ #as may be mrp can be installed without sale module
if field_name=='name':
res[production['id']]=move.sale_line_id and move.sale_line_id.order_id.name or False
if field_name=='client_order_ref':
res[production['id']]=move.sale_line_id and move.sale_line_id.order_id.client_order_ref or False
return res
+ def _production_calc(self, cr, uid, ids, prop, unknow_none, context={}):
+ 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
+
+ def _production_date_end(self, cr, uid, ids, prop, unknow_none, context={}):
+ result = {}
+ for prod in self.browse(cr, uid, ids, context=context):
+ result[prod.id] = prod.date_planned
+ return result
+
+ def _production_date(self, cr, uid, ids, prop, unknow_none, context={}):
+ result = {}
+ for prod in self.browse(cr, uid, ids, context=context):
+ result[prod.id] = prod.date_planned[:10]
+ return result
+
def _sale_name_calc(self, cr, uid, ids, prop, unknow_none, unknow_dict):
return self._get_sale_order(cr,uid,ids,field_name='name')
'priority': fields.selection([('0','Not urgent'),('1','Normal'),('2','Urgent'),('3','Very Urgent')], 'Priority'),
'product_id': fields.many2one('product.product', 'Product', required=True, domain=[('type','<>','service')]),
- 'product_qty': fields.float('Product Qty', required=True),
- 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True),
- 'product_uos_qty': fields.float('Product Qty'),
- 'product_uos': fields.many2one('product.uom', 'Product UOM'),
-
- 'location_src_id': fields.many2one('stock.location', 'Raw Products Location', required=True),
- 'location_dest_id': fields.many2one('stock.location', 'Finnished Products Location', required=True),
-
+ 'product_qty': fields.float('Product Qty', required=True, states={'draft':[('readonly',False)]}, readonly=True),
+ 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True, states={'draft':[('readonly',False)]}, readonly=True),
+ 'product_uos_qty': fields.float('Product UoS Qty', states={'draft':[('readonly',False)]}, readonly=True),
+ 'product_uos': fields.many2one('product.uom', 'Product UoS', states={'draft':[('readonly',False)]}, readonly=True),
+
+ 'location_src_id': fields.many2one('stock.location', 'Raw Materials Location', required=True,
+ help="Location where the system will look for products used in raw materials."),
+ 'location_dest_id': fields.many2one('stock.location', 'Finished Products Location', required=True,
+ help="Location where the system will stock the finished products."),
+
+ 'date_planned_end': fields.function(_production_date_end, method=True, type='date', string='Scheduled End'),
+ 'date_planned_date': fields.function(_production_date, method=True, type='date', string='Scheduled Date'),
'date_planned': fields.datetime('Scheduled date', required=True, select=1),
'date_start': fields.datetime('Start Date'),
'date_finnished': fields.datetime('End Date'),
'bom_id': fields.many2one('mrp.bom', 'Bill of Material', domain=[('bom_id','=',False)]),
+ 'routing_id': fields.many2one('mrp.routing', string='Routing', on_delete='set null', help="The list of operations (list of workcenters) to produce the finished product. The routing is mainly used to compute workcenter costs during operations and to plan futur loads on workcenters based on production plannification."),
- 'picking_id': fields.many2one('stock.picking', 'Packing list', readonly=True),
+ 'picking_id': fields.many2one('stock.picking', 'Picking list', readonly=True,
+ help="This is the internal picking list of the raw material needed for the production plan"),
'move_prod_id': fields.many2one('stock.move', 'Move product', readonly=True),
'move_lines': fields.many2many('stock.move', 'mrp_production_move_ids', 'production_id', 'move_id', 'Products Consummed'),
'move_created_ids': fields.one2many('stock.move', 'production_id', 'Moves Created'),
'product_lines': fields.one2many('mrp.production.product.line', 'production_id', 'Scheduled goods'),
'workcenter_lines': fields.one2many('mrp.production.workcenter.line', 'production_id', 'Workcenters Utilisation'),
+ 'state': fields.selection([('draft','Draft'),('picking_except', 'Picking Exception'),('confirmed','Waiting Goods'),('ready','Ready to Produce'),('in_production','In Production'),('cancel','Cancelled'),('done','Done')],'State', readonly=True,
+ help='When the production order is created the state is set to \'Draft\'.\n If the order is confirmed the state is set to \'Waiting Goods\'.\n If any exceptions are there, the state is set to \'Packing Exception\'.\
+ \nIf the stock is available then the state is set to \'Ready to Produce\'.\n When the production get started then the state is set to \'In Production\'.\n When the production is over, the state is set to \'Done\'.'),
+ 'hour_total': fields.function(_production_calc, method=True, type='float', string='Total Hours', multi='workorder'),
+ 'cycle_total': fields.function(_production_calc, method=True, type='float', string='Total Cycles', multi='workorder'),
- 'state': fields.selection([('draft','Draft'),('picking_except', 'Packing Exception'),('confirmed','Waiting Goods'),('ready','Ready to Produce'),('in_production','In Production'),('cancel','Canceled'),('done','Done')],'Status', readonly=True),
'sale_name': fields.function(_sale_name_calc, method=True, type='char', string='Sale Name'),
'sale_ref': fields.function(_sale_ref_calc, method=True, type='char', string='Sale Ref'),
}
'name': lambda x,y,z,c: x.pool.get('ir.sequence').get(y,z,'mrp.production') or '/',
}
_order = 'date_planned asc, priority desc';
+ def unlink(self, cr, uid, ids, context=None):
+ productions = self.read(cr, uid, ids, ['state'])
+ unlink_ids = []
+ for s in productions:
+ if s['state'] in ['draft','cancel']:
+ unlink_ids.append(s['id'])
+ else:
+ raise osv.except_osv(_('Invalid action !'), _('Cannot delete Production Order(s) which are in %s State!' % s['state']))
+ return osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
+
+ def copy(self, cr, uid, id, default=None,context=None):
+ if not default:
+ default = {}
+ default.update({
+ 'name': self.pool.get('ir.sequence').get(cr, uid, 'mrp.production'),
+ 'move_lines' : [],
+ 'move_created_ids': [],
+ 'state': 'draft'
+ })
+ return super(mrp_production, self).copy(cr, uid, id, default, context)
+
+ def location_id_change(self, cr, uid, ids, src, dest, context={}):
+ if dest:
+ return {}
+ if src:
+ return {'value': {'location_dest_id': src}}
+ return {}
def product_id_change(self, cr, uid, ids, product):
if not product:
result = {'product_uom':uom}
return {'value':result}
+ def bom_id_change(self, cr, uid, ids, product):
+ if not product:
+ return {}
+ res = self.pool.get('mrp.bom').read(cr, uid, [product], ['routing_id'])[0]
+ routing_id = res['routing_id'] and res['routing_id'][0]
+ result = {'routing_id':routing_id}
+ return {'value':result}
+
def action_picking_except(self, cr, uid, ids):
self.write(cr, uid, ids, {'state':'picking_except'})
return True
def action_compute(self, cr, uid, ids, properties=[]):
results = []
for production in self.browse(cr, uid, ids):
- cr.execute('delete from mrp_production_product_line where production_id=%d', (production.id,))
- cr.execute('delete from mrp_production_workcenter_line where production_id=%d', (production.id,))
+ cr.execute('delete from mrp_production_product_line where production_id=%s', (production.id,))
+ cr.execute('delete from mrp_production_workcenter_line where production_id=%s', (production.id,))
bom_point = production.bom_id
bom_id = production.bom_id.id
if not bom_point:
bom_id = self.pool.get('mrp.bom')._bom_find(cr, uid, production.product_id.id, production.product_uom.id, properties)
if bom_id:
- self.write(cr, uid, [production.id], {'bom_id': bom_id})
- bom_point = self.pool.get('mrp.bom').browse(cr, uid, [bom_id])[0]
+ bom_point = self.pool.get('mrp.bom').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', "Couldn't find bill of material for product")
+ raise osv.except_osv(_('Error'), _("Couldn't find bill of material for product"))
#if bom_point.routing_id and bom_point.routing_id.location_id:
# self.write(cr, uid, [production.id], {'location_src_id': bom_point.routing_id.location_id.id})
if production.move_created_ids:
self.pool.get('stock.move').action_cancel(cr, uid, [x.id for x in production.move_created_ids])
self.pool.get('stock.move').action_cancel(cr, uid, [x.id for x in production.move_lines])
- self.write(cr, uid, ids, {'state':'cancel','move_lines':[(6,0,[])]})
+ self.write(cr, uid, ids, {'state':'cancel'}) #,'move_lines':[(6,0,[])]})
return True
#XXX: may be a bug here; lot_lines are unreserved for a few seconds;
for move in production.move_created_ids:
#XXX must use the orm
cr.execute('INSERT INTO stock_move_history_ids \
- (parent_id, child_id) VALUES (%d,%d)',
+ (parent_id, child_id) VALUES (%s,%s)',
(res.id, move.id))
move_ids.append(res.id)
- if production.move_created_ids:
- #TODO There we should handle the residus move creation
- vals= {'state':'confirmed'}
- new_moves = [x.id for x in production.move_created_ids]
- self.pool.get('stock.move').write(cr, uid, new_moves, vals)
- else:
- #XXX Why is it there ? Aren't we suppose to already have a created_move ?
- source = production.product_id.product_tmpl_id.property_stock_production.id
- vals = {
- 'name':'PROD:'+production.name,
- 'date_planned': production.date_planned,
- 'product_id': production.product_id.id,
- 'product_qty': production.product_qty,
- 'product_uom': production.product_uom.id,
- '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_dest_id': production.location_dest_id.id,
- 'move_dest_id': production.move_prod_id.id,
- 'state': 'confirmed'
- }
- new_moves = [self.pool.get('stock.move').create(cr, uid, vals)]
- self.write(cr, uid, [production.id],
- {'move_created_ids': [(6, 'WTF', new_moves)]})
+ vals= {'state':'confirmed'}
+ new_moves = [x.id for x in production.move_created_ids]
+ self.pool.get('stock.move').write(cr, uid, new_moves, vals)
if not production.date_finnished:
self.write(cr, uid, [production.id],
{'date_finnished': time.strftime('%Y-%m-%d %H:%M:%S')})
def action_confirm(self, cr, uid, ids):
picking_id=False
+ proc_ids = []
for production in self.browse(cr, uid, ids):
if not production.product_lines:
self.action_compute(cr, uid, [production.id])
'address_id': address_id,
'auto_picking': self._get_auto_picking(cr, uid, production),
})
- toconfirm = True
source = production.product_id.product_tmpl_id.property_stock_production.id
data = {
'state': 'waiting'
}
res_final_id = self.pool.get('stock.move').create(cr, uid, data)
-
- self.write(cr, uid, [production.id], {'move_created_ids': [(6, 'WTF', [res_final_id])]})
+
+ self.write(cr, uid, [production.id], {'move_created_ids': [(6, 0, [res_final_id])]})
moves = []
for line in production.product_lines:
move_id=False
})
wf_service = netsvc.LocalService("workflow")
wf_service.trg_validate(uid, 'mrp.procurement', proc_id, 'button_confirm', cr)
- if toconfirm:
- wf_service = netsvc.LocalService("workflow")
- wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
+ proc_ids.append(proc_id)
+ wf_service = netsvc.LocalService("workflow")
+ wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
self.write(cr, uid, [production.id], {'picking_id':picking_id, 'move_lines': [(6,0,moves)], 'state':'confirmed'})
return picking_id
class mrp_production_workcenter_line(osv.osv):
_name = 'mrp.production.workcenter.line'
- _description = 'Production workcenters used'
+ _description = 'Work Orders'
+ _order = 'sequence'
_columns = {
- 'name': fields.char('Name', size=64, required=True),
+ 'name': fields.char('Work Order', size=64, required=True),
'workcenter_id': fields.many2one('mrp.workcenter', 'Workcenter', required=True),
- 'cycle': fields.float('Nbr of cycle'),
- 'hour': fields.float('Nbr of hour'),
+ 'cycle': fields.float('Nbr of cycle', digits=(16,2)),
+ 'hour': fields.float('Nbr of hour', digits=(16,2)),
'sequence': fields.integer('Sequence', required=True),
- 'production_id': fields.many2one('mrp.production', 'Production Order', select=True),
+ 'production_id': fields.many2one('mrp.production', 'Production Order', select=True, ondelete='cascade'),
}
_defaults = {
'sequence': lambda *a: 1,
class mrp_procurement(osv.osv):
_name = "mrp.procurement"
_description = "Procurement"
+ _order = 'priority,date_planned'
_columns = {
'name': fields.char('Name', size=64, required=True),
- 'origin': fields.char('Origin', size=64),
+ 'origin': fields.char('Origin', size=64,
+ help="Reference of the document that created this Requisition.\n"
+ "This is automatically completed by Open ERP."),
'priority': fields.selection([('0','Not urgent'),('1','Normal'),('2','Urgent'),('3','Very Urgent')], 'Priority', required=True),
'date_planned': fields.datetime('Scheduled date', required=True),
'date_close': fields.datetime('Date Closed'),
'product_id': fields.many2one('product.product', 'Product', required=True),
- 'product_qty': fields.float('Quantity', required=True),
- 'product_uom': fields.many2one('product.uom', 'Product UoM', required=True),
- 'product_uos_qty': fields.float('UoS Quantity'),
- 'product_uos': fields.many2one('product.uom', 'Product UoS'),
+ 'product_qty': fields.float('Quantity', required=True, states={'draft':[('readonly',False)]}, readonly=True),
+ 'product_uom': fields.many2one('product.uom', 'Product UoM', required=True, states={'draft':[('readonly',False)]}, readonly=True),
+ 'product_uos_qty': fields.float('UoS Quantity', states={'draft':[('readonly',False)]}, readonly=True),
+ 'product_uos': fields.many2one('product.uom', 'Product UoS', states={'draft':[('readonly',False)]}, readonly=True),
'move_id': fields.many2one('stock.move', 'Reservation', ondelete='set null'),
'bom_id': fields.many2one('mrp.bom', 'BoM', ondelete='cascade', select=True),
'close_move': fields.boolean('Close Move at end', required=True),
'location_id': fields.many2one('stock.location', 'Location', required=True),
- 'procure_method': fields.selection([('make_to_stock','from stock'),('make_to_order','on order')], 'Procurement Method', states={'draft':[('readonly',False)], 'confirmed':[('readonly',False)]}, readonly=True, required=True),
+ 'procure_method': fields.selection([('make_to_stock','from stock'),('make_to_order','on order')], 'Requisition Method', states={'draft':[('readonly',False)], 'confirmed':[('readonly',False)]},
+ readonly=True, required=True, help="If you encode manually a Requisition, you probably want to use" \
+ " a make to order method."),
'purchase_id': fields.many2one('purchase.order', 'Purchase Order'),
- 'purchase_line_id': fields.many2one('purchase.order.line', 'Purchase Order Line'),
+ 'note': fields.text('Note'),
'property_ids': fields.many2many('mrp.property', 'mrp_procurement_property_rel', 'procurement_id','property_id', 'Properties'),
'message': fields.char('Latest error', size=64),
- 'state': fields.selection([('draft','Draft'),('confirmed','Confirmed'),('exception','Exception'),('running','Running'),('cancel','Cancel'),('done','Done'),('waiting','Waiting')], 'Status')
+ 'state': fields.selection([
+ ('draft','Draft'),
+ ('confirmed','Confirmed'),
+ ('exception','Exception'),
+ ('running','Running'),
+ ('cancel','Cancel'),
+ ('ready','Ready'),
+ ('done','Done'),
+ ('waiting','Waiting')], 'State', required=True,
+ help='When a procurement is created the state is set to \'Draft\'.\n If the procurement is confirmed, the state is set to \'Confirmed\'.\
+ \nAfter confirming the state is set to \'Running\'.\n If any exception arises in the order then the state is set to \'Exception\'.\n Once the exception is removed the state becomes \'Ready\'.\n It is in \'Waiting\'. state when the procurement is waiting for another one to finish.'),
+ 'note' : fields.text('Note'),
}
_defaults = {
'state': lambda *a: 'draft',
'close_move': lambda *a: 0,
'procure_method': lambda *a: 'make_to_order',
}
+
+ def unlink(self, cr, uid, ids, context=None):
+ procurements = self.read(cr, uid, ids, ['state'])
+ unlink_ids = []
+ for s in procurements:
+ if s['state'] in ['draft','cancel']:
+ unlink_ids.append(s['id'])
+ else:
+ raise osv.except_osv(_('Invalid action !'), _('Cannot delete Requisition Order(s) which are in %s State!' % s['state']))
+ return osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
+
+ def onchange_product_id(self, cr, uid, ids, product_id, context={}):
+ if product_id:
+ w=self.pool.get('product.product').browse(cr,uid,product_id, context)
+ v = {
+ 'product_uom':w.uom_id.id,
+ 'product_uos':w.uos_id and w.uos_id.id or w.uom_id.id
+ }
+ return {'value': v}
+ return {}
+
def check_product(self, cr, uid, ids):
for procurement in self.browse(cr, uid, ids):
if procurement.product_id.type in ('product', 'consu'):
def check_move_cancel(self, cr, uid, ids, context={}):
res = True
+ ok = False
for procurement in self.browse(cr, uid, ids, context):
if procurement.move_id:
+ ok = True
if not procurement.move_id.state=='cancel':
res = False
- return res
+ return res and ok
def check_move_done(self, cr, uid, ids, context={}):
res = True
#
def _quantity_compute_get(self, cr, uid, proc, context={}):
if proc.product_id.type=='product':
- return proc.move_id.product_uos_qty
+ if proc.move_id.product_uos:
+ return proc.move_id.product_uos_qty
return False
def _uom_compute_get(self, cr, uid, proc, context={}):
properties = [x.id for x in procurement.property_ids]
bom_id = self.pool.get('mrp.bom')._bom_find(cr, uid, procurement.product_id.id, procurement.product_uom.id, properties)
if not bom_id:
- cr.execute('update mrp_procurement set message=%s where id=%d', ('No BoM defined for this product !', procurement.id))
+ cr.execute('update mrp_procurement set message=%s where id=%s', (_('No BoM defined for this product !'), procurement.id))
return False
return True
res = True
user = self.pool.get('res.users').browse(cr, uid, uid)
for procurement in self.browse(cr, uid, ids):
- if procurement.product_id.product_tmpl_id.supply_method=='buy':
+ if procurement.product_id.product_tmpl_id.supply_method<>'produce':
if procurement.product_id.seller_ids:
partner = procurement.product_id.seller_ids[0].name
if user.company_id and user.company_id.partner_id:
def check_buy(self, cr, uid, ids):
user = self.pool.get('res.users').browse(cr, uid, uid)
for procurement in self.browse(cr, uid, ids):
- if procurement.product_id.product_tmpl_id.supply_method=='produce':
+ if procurement.product_id.product_tmpl_id.supply_method<>'buy':
return False
if not procurement.product_id.seller_ids:
- cr.execute('update mrp_procurement set message=%s where id=%d', ('No supplier defined for this product !', procurement.id))
+ cr.execute('update mrp_procurement set message=%s where id=%s', (_('No supplier defined for this product !'), procurement.id))
return False
partner = procurement.product_id.seller_ids[0].name
if user.company_id and user.company_id.partner_id:
return False
address_id = self.pool.get('res.partner').address_get(cr, uid, [partner.id], ['delivery'])['delivery']
if not address_id:
- cr.execute('update mrp_procurement set message=%s where id=%d', ('No address defined for the supplier', procurement.id))
+ cr.execute('update mrp_procurement set message=%s where id=%s', (_('No address defined for the supplier'), procurement.id))
return False
return True
return True
return False
- def action_confirm(self, cr, uid, ids):
+ def action_confirm(self, cr, uid, ids, context={}):
for procurement in self.browse(cr, uid, ids):
+ if procurement.product_qty <= 0.00:
+ raise osv.except_osv(_('Data Insufficient !'), _('Please check the Quantity of Requisition Order(s), it should not be less than 1!'))
if procurement.product_id.type in ('product', 'consu'):
if not procurement.move_id:
source = procurement.location_id.id
return True
def action_move_assigned(self, cr, uid, ids):
- self.write(cr, uid, ids, {'state':'running','message':'from stock: products assigned.'})
+ self.write(cr, uid, ids, {'state':'running','message':_('from stock: products assigned.')})
return True
def _check_make_to_stock_service(self, cr, uid, procurement, context={}):
id = procurement.move_id.id
if not (procurement.move_id.state in ('done','assigned','cancel')):
ok = ok and self.pool.get('stock.move').action_assign(cr, uid, [id])
- cr.execute('select count(id) from stock_warehouse_orderpoint where product_id=%d', (procurement.product_id.id,))
+ cr.execute('select count(id) from stock_warehouse_orderpoint where product_id=%s', (procurement.product_id.id,))
if not cr.fetchone()[0]:
- cr.execute('update mrp_procurement set message=%s where id=%d', ('from stock and no minimum orderpoint rule defined', procurement.id))
+ cr.execute('update mrp_procurement set message=%s where id=%s', (_('from stock and no minimum orderpoint rule defined'), procurement.id))
return ok
def action_produce_assign_service(self, cr, uid, ids, context={}):
'location_src_id': procurement.location_id.id,
'location_dest_id': procurement.location_id.id,
'bom_id': procurement.bom_id and procurement.bom_id.id or False,
- 'date_planned': newdate,
+ 'date_planned': newdate.strftime('%Y-%m-%d %H:%M:%S'),
'move_prod_id': res_id,
})
self.write(cr, uid, [procurement.id], {'state':'running'})
[produce_id], properties=[x.id for x in procurement.property_ids])
wf_service = netsvc.LocalService("workflow")
wf_service.trg_validate(uid, 'mrp.production', produce_id, 'button_confirm', cr)
+ self.pool.get('stock.move').write(cr, uid, [res_id],
+ {'location_id':procurement.location_id.id})
return produce_id
def action_po_assign(self, cr, uid, ids, context={}):
price = self.pool.get('product.pricelist').price_get(cr, uid, [pricelist_id], procurement.product_id.id, qty, False, {'uom': uom_id})[pricelist_id]
- newdate = DateTime.strptime(procurement.date_planned, '%Y-%m-%d %H:%M:%S') - DateTime.RelativeDateTime(days=procurement.product_id.product_tmpl_id.seller_delay or 0.0)
+ newdate = DateTime.strptime(procurement.date_planned, '%Y-%m-%d %H:%M:%S')
newdate = newdate - DateTime.RelativeDateTime(days=company.po_lead)
+ newdate = newdate - procurement.product_id.seller_ids[0].delay
+
+ context.update({'lang':partner.lang})
+ product=self.pool.get('product.product').browse(cr,uid,procurement.product_id.id,context=context)
+
line = {
- 'name': procurement.product_id.name,
+ 'name': product.name,
'product_qty': qty,
'product_id': procurement.product_id.id,
'product_uom': uom_id,
'price_unit': price,
'date_planned': newdate.strftime('%Y-%m-%d %H:%M:%S'),
- 'taxes_id': [(6, 0, [x.id for x in procurement.product_id.product_tmpl_id.supplier_taxes_id])],
'move_dest_id': res_id,
+ 'notes':product.description_purchase,
}
+ taxes_ids = procurement.product_id.product_tmpl_id.supplier_taxes_id
+ taxes = self.pool.get('account.fiscal.position').map_tax(cr, uid, partner.property_account_position, taxes_ids)
+ line.update({
+ 'taxes_id':[(6,0,taxes)]
+ })
purchase_id = self.pool.get('purchase.order').create(cr, uid, {
'origin': procurement.origin,
'partner_id': partner_id,
'partner_address_id': address_id,
'location_id': procurement.location_id.id,
'pricelist_id': pricelist_id,
- 'order_line': [(0,0,line)]
+ 'order_line': [(0,0,line)],
+ 'fiscal_position': partner.property_account_position and partner.property_account_position.id or False
})
self.write(cr, uid, [procurement.id], {'state':'running', 'purchase_id':purchase_id})
return purchase_id
def action_cancel(self, cr, uid, ids):
todo = []
+ todo2 = []
for proc in self.browse(cr, uid, ids):
- if proc.move_id:
- todo.append(proc.move_id.id)
+ if proc.close_move:
+ if proc.move_id.state not in ('done','cancel'):
+ todo2.append(proc.move_id.id)
+ else:
+ if proc.move_id and proc.move_id.state=='waiting':
+ todo.append(proc.move_id.id)
+ if len(todo2):
+ self.pool.get('stock.move').action_cancel(cr, uid, todo2)
if len(todo):
- self.pool.get('stock.move').action_cancel(cr, uid, [proc.move_id.id])
+ self.pool.get('stock.move').write(cr, uid, todo, {'state':'assigned'})
self.write(cr, uid, ids, {'state':'cancel'})
-
wf_service = netsvc.LocalService("workflow")
for id in ids:
wf_service.trg_trigger(uid, 'mrp.procurement', id, cr)
-
return True
def action_check_finnished(self, cr, uid, ids):
- return True
+ return self.check_move_done(cr, uid, ids)
def action_check(self, cr, uid, ids):
ok = False
ok = True
return ok
+ def action_ready(self, cr, uid, ids):
+ res = self.write(cr, uid, ids, {'state':'ready'})
+ return res
+
def action_done(self, cr, uid, ids):
for procurement in self.browse(cr, uid, ids):
if procurement.move_id:
if procurement.close_move and (procurement.move_id.state <> 'done'):
self.pool.get('stock.move').action_done(cr, uid, [procurement.move_id.id])
res = self.write(cr, uid, ids, {'state':'done', 'date_close':time.strftime('%Y-%m-%d')})
-
wf_service = netsvc.LocalService("workflow")
for id in ids:
wf_service.trg_trigger(uid, 'mrp.procurement', id, cr)
return res
+
def run_scheduler(self, cr, uid, automatic=False, use_new_cursor=False, context=None):
'''
use_new_cursor: False or the dbname
'location_id': fields.many2one('stock.location', 'Location', required=True),
'product_id': fields.many2one('product.product', 'Product', required=True, domain=[('type','=','product')]),
'product_uom': fields.many2one('product.uom', 'Product UOM', required=True ),
- 'product_min_qty': fields.float('Min Quantity', required=True),
- 'product_max_qty': fields.float('Max Quantity', required=True),
- 'qty_multiple': fields.integer('Qty Multiple', required=True),
+ 'product_min_qty': fields.float('Min Quantity', required=True,
+ help="When the virtual stock goes belong the Min Quantity, Open ERP generates "\
+ "a requisition to bring the virtual stock to the Max Quantity."),
+ 'product_max_qty': fields.float('Max Quantity', required=True,
+ help="When the virtual stock goes belong the Min Quantity, Open ERP generates "\
+ "a requisition to bring the virtual stock to the Max Quantity."),
+ 'qty_multiple': fields.integer('Qty Multiple', required=True,
+ help="The requisition quantity will by rounded up to this multiple."),
'procurement_id': fields.many2one('mrp.procurement', 'Purchase Order')
}
_defaults = {
v = {'product_uom':prod.uom_id.id}
return {'value': v}
return {}
+ def copy(self, cr, uid, id, default=None,context={}):
+ if not default:
+ default = {}
+ default.update({
+ 'name': self.pool.get('ir.sequence').get(cr, uid, 'mrp.warehouse.orderpoint') or '',
+ })
+ return super(stock_warehouse_orderpoint, self).copy(cr, uid, id, default, context)
stock_warehouse_orderpoint()
class StockMove(osv.osv):
_inherit = 'stock.move'
_columns = {
- 'procurements': fields.one2many('mrp.procurement', 'move_id', 'Procurements'),
+ 'procurements': fields.one2many('mrp.procurement', 'move_id', 'Requisitions'),
}
+ def copy(self, cr, uid, id, default=None, context=None):
+ default = default or {}
+ default['procurements'] = []
+ return super(StockMove, self).copy(cr, uid, id, default, context)
+
def _action_explode(self, cr, uid, move, context={}):
if move.product_id.supply_method=='produce' and move.product_id.procure_method=='make_to_order':
bis = self.pool.get('mrp.bom').search(cr, uid, [
'product_uos_qty': line['product_uos_qty'],
'move_dest_id': move.id,
'state': state,
+ 'name': line['name'],
'location_dest_id': dest,
'move_history_ids': [(6,0,[move.id])],
'move_history_ids2': [(6,0,[])],
# Explode picking by replacing phantom BoMs
#
def action_explode(self, cr, uid, picks, *args):
- for move in picks:
+ for move in self.pool.get('stock.move').browse(cr, uid, picks):
self.pool.get('stock.move')._action_explode(cr, uid, move)
return picks