1 # -*- encoding: utf-8 -*-
2 ##############################################################################
4 # Copyright (c) 2004-2008 TINY SPRL. (http://tiny.be) All Rights Reserved.
8 # WARNING: This program as such is intended to be used by professional
9 # programmers who take the whole responsability of assessing all potential
10 # consequences resulting from its eventual inadequacies and bugs
11 # End users who are looking for a ready-to-use solution with commercial
12 # garantees and support are strongly adviced to contract a Free Software
15 # This program is Free Software; you can redistribute it and/or
16 # modify it under the terms of the GNU General Public License
17 # as published by the Free Software Foundation; either version 2
18 # of the License, or (at your option) any later version.
20 # This program is distributed in the hope that it will be useful,
21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 # GNU General Public License for more details.
25 # You should have received a copy of the GNU General Public License
26 # along with this program; if not, write to the Free Software
27 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
29 ##############################################################################
31 from osv import fields
37 from mx import DateTime
39 #----------------------------------------------------------
41 #----------------------------------------------------------
42 # capacity_hour : capacity per hour. default: 1.0.
43 # Eg: If 5 concurrent operations at one time: capacity = 5 (because 5 employees)
44 # unit_per_cycle : how many units are produced for one cycle
46 # TODO: Work Center may be recursive ?
48 class mrp_workcenter(osv.osv):
49 _name = 'mrp.workcenter'
50 _description = 'Workcenter'
52 'name': fields.char('Workcenter Name', size=64, required=True),
53 'active': fields.boolean('Active'),
54 'type': fields.selection([('machine','Machine'),('hr','Human Resource'),('tool','Tool')], 'Type', required=True),
55 'code': fields.char('Code', size=16),
56 'timesheet_id': fields.many2one('hr.timesheet.group', 'Working Time', help="The normal working time of the workcenter."),
57 'note': fields.text('Description', help="Description of the workcenter. Explain here what's a cycle according to this workcenter."),
59 'capacity_per_cycle': fields.float('Capacity per Cycle', help="Number of operation this workcenter can do in parallel. If this workcenter represent a team of 5 workers, the capacity per cycle is 5."),
61 'time_cycle': fields.float('Time for 1 cycle (hour)', help="Time in hours for doing one cycle."),
62 'time_start': fields.float('Time before prod.', help="Time in hours for the setup."),
63 'time_stop': fields.float('Time after prod.', help="Time in hours for the cleaning."),
64 'time_efficiency': fields.float('Time Efficiency', help="Factor that multiplies all times expressed in the workcenter."),
66 'costs_hour': fields.float('Cost per hour'),
67 'costs_hour_account_id': fields.many2one('account.analytic.account', 'Hour Account', domain=[('type','<>','view')],
68 help="Complete this only if you want automatic analytic accounting entries on production orders."),
69 'costs_cycle': fields.float('Cost per cycle'),
70 'costs_cycle_account_id': fields.many2one('account.analytic.account', 'Cycle Account', domain=[('type','<>','view')],
71 help="Complete this only if you want automatic analytic accounting entries on production orders."),
72 'costs_journal_id': fields.many2one('account.analytic.journal', 'Analytic Journal'),
73 'costs_general_account_id': fields.many2one('account.account', 'General Account', domain=[('type','<>','view')]),
76 'active': lambda *a: 1,
77 'type': lambda *a: 'machine',
78 'time_efficiency': lambda *a: 1.0,
79 'capacity_per_cycle': lambda *a: 1.0,
84 class mrp_property_group(osv.osv):
85 _name = 'mrp.property.group'
86 _description = 'Property Group'
88 'name': fields.char('Property Group', size=64, required=True),
89 'description': fields.text('Description'),
93 class mrp_property(osv.osv):
94 _name = 'mrp.property'
95 _description = 'Property'
97 'name': fields.char('Name', size=64, required=True),
98 'composition': fields.selection([('min','min'),('max','max'),('plus','plus')], 'Properties composition', required=True, help="Not used in computations, for information purpose only."),
99 'group_id': fields.many2one('mrp.property.group', 'Property Group', required=True),
100 'description': fields.text('Description'),
103 'composition': lambda *a: 'min',
107 class mrp_routing(osv.osv):
108 _name = 'mrp.routing'
109 _description = 'Routing'
111 'name': fields.char('Name', size=64, required=True),
112 'active': fields.boolean('Active'),
113 'code': fields.char('Code', size=8),
115 'note': fields.text('Description'),
116 'workcenter_lines': fields.one2many('mrp.routing.workcenter', 'routing_id', 'Workcenters'),
118 'location_id': fields.many2one('stock.location', 'Production Location',
119 help="Keep empty if you produce at the location where the finnished products are needed." \
120 "Put a location if you produce at a fixed location. This can be a partner location " \
121 "if you subcontract the manufacturing operations."
125 'active': lambda *a: 1,
129 class mrp_routing_workcenter(osv.osv):
130 _name = 'mrp.routing.workcenter'
131 _description = 'Routing workcenter usage'
133 'workcenter_id': fields.many2one('mrp.workcenter', 'Workcenter', required=True),
134 'name': fields.char('Name', size=64, required=True),
135 'sequence': fields.integer('Sequence'),
136 'cycle_nbr': fields.float('Number of Cycle', required=True,
137 help="A cycle is defined in the workcenter definition."),
138 'hour_nbr': fields.float('Number of Hours', required=True),
139 'routing_id': fields.many2one('mrp.routing', 'Parent Routing', select=True),
140 'note': fields.text('Description')
143 'cycle_nbr': lambda *a: 1.0,
144 'hour_nbr': lambda *a: 0.0,
146 mrp_routing_workcenter()
148 class mrp_bom(osv.osv):
150 _description = 'Bill of Material'
151 def _child_compute(self, cr, uid, ids, name, arg, context={}):
153 for bom in self.browse(cr, uid, ids, context=context):
154 result[bom.id] = map(lambda x: x.id, bom.bom_lines)
155 ok = ((name=='child_complete_ids') and (bom.product_id.supply_method=='produce'))
156 if bom.type=='phantom' or ok:
157 sids = self.pool.get('mrp.bom').search(cr, uid, [('bom_id','=',False),('product_id','=',bom.product_id.id)])
159 bom2 = self.pool.get('mrp.bom').browse(cr, uid, sids[0], context=context)
160 result[bom.id] += map(lambda x: x.id, bom2.bom_lines)
162 def _compute_type(self, cr, uid, ids, field_name, arg, context):
163 res = dict(map(lambda x: (x,''), ids))
164 for line in self.browse(cr, uid, ids):
165 if line.type=='phantom' and not line.bom_id:
168 if line.bom_lines or line.type=='phantom':
170 if line.product_id.supply_method=='produce':
171 if line.product_id.procure_method=='make_to_stock':
172 res[line.id] = 'stock'
174 res[line.id] = 'order'
177 'name': fields.char('Name', size=64, required=True),
178 'code': fields.char('Code', size=16),
179 'active': fields.boolean('Active'),
180 'type': fields.selection([('normal','Normal BoM'),('phantom','Sets / Phantom')], 'BoM Type', required=True, help=
181 "Use a phantom bill of material in raw materials lines that have to be " \
182 "automatically computed in on eproduction order and not one per level." \
183 "If you put \"Phantom/Set\" at the root level of a bill of material " \
184 "it is considered as a set or pack: the products are replaced by the components " \
185 "between the sale order to the picking without going through the production order." \
186 "The normal BoM will generate one production order per BoM level."),
187 'method': fields.function(_compute_type, string='Method', method=True, type='selection', selection=[('',''),('stock','On Stock'),('order','On Order'),('set','Set / Pack')]),
188 'date_start': fields.date('Valid From', help="Validity of this BoM or component. Keep empty if it's always valid."),
189 'date_stop': fields.date('Valid Until', help="Validity of this BoM or component. Keep empty if it's always valid."),
190 'sequence': fields.integer('Sequence'),
191 'position': fields.char('Internal Ref.', size=64, help="Reference to a position in an external plan."),
192 'product_id': fields.many2one('product.product', 'Product', required=True),
193 'product_uos_qty': fields.float('Product UOS Qty'),
194 'product_uos': fields.many2one('product.uom', 'Product UOS'),
195 'product_qty': fields.float('Product Qty', required=True),
196 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True),
197 'product_rounding': fields.float('Product Rounding', help="Rounding applied on the product quantity. For integer only values, put 1.0"),
198 'product_efficiency': fields.float('Product Efficiency', required=True, help="Efficiency on the production. A factor of 0.9 means a loss of 10% in the production."),
199 'bom_lines': fields.one2many('mrp.bom', 'bom_id', 'BoM Lines'),
200 'bom_id': fields.many2one('mrp.bom', 'Parent BoM', ondelete='cascade', select=True),
201 '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."),
202 'property_ids': fields.many2many('mrp.property', 'mrp_bom_property_rel', 'bom_id','property_id', 'Properties'),
203 'revision_ids': fields.one2many('mrp.bom.revision', 'bom_id', 'BoM Revisions'),
204 'revision_type': fields.selection([('numeric','numeric indices'),('alpha','alphabetical indices')], 'indice type'),
205 'child_ids': fields.function(_child_compute,relation='mrp.bom', method=True, string="BoM Hyerarchy", type='many2many'),
206 'child_complete_ids': fields.function(_child_compute,relation='mrp.bom', method=True, string="BoM Hyerarchy", type='many2many')
209 'active': lambda *a: 1,
210 'product_efficiency': lambda *a: 1.0,
211 'product_qty': lambda *a: 1.0,
212 'product_rounding': lambda *a: 1.0,
213 'type': lambda *a: 'normal',
217 ('bom_qty_zero', 'CHECK (product_qty>0)', 'All product quantities must be greater than 0.\n' \
218 'You should install the mrp_subproduct module if you want to manage extra products on BoMs !'),
221 def _check_recursion(self, cr, uid, ids):
224 cr.execute('select distinct bom_id from mrp_bom where id in ('+','.join(map(str,ids))+')')
225 ids = filter(None, map(lambda x:x[0], cr.fetchall()))
231 (_check_recursion, 'Error ! You can not create recursive BoM.', ['parent_id'])
235 def onchange_product_id(self, cr, uid, ids, product_id, name, context={}):
237 prod=self.pool.get('product.product').browse(cr,uid,[product_id])[0]
238 v = {'product_uom':prod.uom_id.id}
240 v['name'] = prod.name
244 def _bom_find(self, cr, uid, product_id, product_uom, properties = []):
246 # Why searching on BoM without parent ?
247 cr.execute('select id from mrp_bom where product_id=%d and bom_id is null order by sequence', (product_id,))
248 ids = map(lambda x: x[0], cr.fetchall())
251 for bom in self.pool.get('mrp.bom').browse(cr, uid, ids):
253 for prop_id in bom.property_ids:
254 if prop_id.id in properties:
256 if (prop>max_prop) or ((max_prop==0) and not result):
260 def _bom_explode(self, cr, uid, bom, factor, properties, addthis=False, level=10):
261 factor = factor / (bom.product_efficiency or 1.0)
262 factor = rounding(factor, bom.product_rounding)
263 if factor<bom.product_rounding:
264 factor = bom.product_rounding
267 if bom.type=='phantom' and not bom.bom_lines:
268 newbom = self._bom_find(cr, uid, bom.product_id.id, bom.product_uom.id, properties)
270 res = self._bom_explode(cr, uid, self.browse(cr, uid, [newbom])[0], factor*bom.product_qty, properties, addthis=True, level=level+10)
271 result = result + res[0]
272 result2 = result2 + res[1]
276 if addthis and not bom.bom_lines:
279 'name': bom.product_id.name,
280 'product_id': bom.product_id.id,
281 'product_qty': bom.product_qty * factor,
282 'product_uom': bom.product_uom.id,
283 'product_uos_qty': bom.product_uos and bom.product_uos_qty * factor or False,
284 'product_uos': bom.product_uos and bom.product_uos.id or False,
287 for wc_use in bom.routing_id.workcenter_lines:
288 wc = wc_use.workcenter_id
289 d, m = divmod(factor, wc_use.workcenter_id.capacity_per_cycle)
290 cycle = (d + (m and 1.0 or 0.0)) * wc_use.cycle_nbr
292 'name': bom.routing_id.name,
293 'workcenter_id': wc.id,
296 'hour': wc_use.hour_nbr + (wc.time_start+wc.time_stop+cycle*wc.time_cycle) * (wc.time_efficiency or 1.0),
298 for bom2 in bom.bom_lines:
299 res = self._bom_explode(cr, uid, bom2, factor, properties, addthis=True, level=level+10)
300 result = result + res[0]
301 result2 = result2 + res[1]
302 return result, result2
304 def set_indices(self, cr, uid, ids, context = {}):
305 if not ids or (ids and not ids[0]):
307 res = self.read(cr, uid, ids, ['revision_ids', 'revision_type'])
308 rev_ids = res[0]['revision_ids']
311 for rev_id in rev_ids:
312 if res[0]['revision_type'] == 'numeric':
313 self.pool.get('mrp.bom.revision').write(cr, uid, [rev_id], {'indice' : idx})
315 self.pool.get('mrp.bom.revision').write(cr, uid, [rev_id], {'indice' : "%c"%(idx+96,)})
321 class mrp_bom_revision(osv.osv):
322 _name = 'mrp.bom.revision'
323 _description = 'Bill of material revisions'
325 'name': fields.char('Modification name', size=64, required=True),
326 'description': fields.text('Description'),
327 'date': fields.date('Modification Date'),
328 'indice': fields.char('Revision', size=16),
329 'last_indice': fields.char('last indice', size=64),
330 'author_id': fields.many2one('res.users', 'Author'),
331 'bom_id': fields.many2one('mrp.bom', 'BoM', select=True),
335 'author_id': lambda x,y,z,c: z,
336 'date': lambda *a: time.strftime('%Y-%m-%d'),
344 return round(f / r) * r
346 class mrp_production(osv.osv):
347 _name = 'mrp.production'
348 _description = 'Production'
349 _date_name = 'date_planned'
351 def _get_sale_order(self,cr,uid,ids,field_name=False):
352 move_obj=self.pool.get('stock.move')
353 def get_parent_move(move_id):
354 move = move_obj.browse(cr,uid,move_id)
355 if move.move_dest_id:
356 return get_parent_move(move.move_dest_id.id)
358 productions=self.read(cr,uid,ids,['id','move_prod_id'])
360 for production in productions:
361 res[production['id']]=False
362 if production.get('move_prod_id',False):
363 parent_move_line=get_parent_move(production['move_prod_id'][0])
365 move = move_obj.browse(cr,uid,parent_move_line)
366 if field_name=='name':
367 res[production['id']]=move.sale_line_id and move.sale_line_id.order_id.name or False
368 if field_name=='client_order_ref':
369 res[production['id']]=move.sale_line_id and move.sale_line_id.order_id.client_order_ref or False
372 def _sale_name_calc(self, cr, uid, ids, prop, unknow_none, unknow_dict):
373 return self._get_sale_order(cr,uid,ids,field_name='name')
375 def _sale_ref_calc(self, cr, uid, ids, prop, unknow_none, unknow_dict):
376 return self._get_sale_order(cr,uid,ids,field_name='client_order_ref')
379 'name': fields.char('Reference', size=64, required=True),
380 'origin': fields.char('Origin', size=64),
381 'priority': fields.selection([('0','Not urgent'),('1','Normal'),('2','Urgent'),('3','Very Urgent')], 'Priority'),
383 'product_id': fields.many2one('product.product', 'Product', required=True, domain=[('type','<>','service')]),
384 'product_qty': fields.float('Product Qty', required=True),
385 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True),
386 'product_uos_qty': fields.float('Product Qty'),
387 'product_uos': fields.many2one('product.uom', 'Product UOM'),
389 'location_src_id': fields.many2one('stock.location', 'Raw Products Location', required=True,
390 help="Location where the system will look for products used in raw materials."),
391 'location_dest_id': fields.many2one('stock.location', 'Finnished Products Location', required=True,
392 help="Location where the system will stock the finnished products."),
394 'date_planned': fields.datetime('Scheduled date', required=True, select=1),
395 'date_start': fields.datetime('Start Date'),
396 'date_finnished': fields.datetime('End Date'),
398 'bom_id': fields.many2one('mrp.bom', 'Bill of Material', domain=[('bom_id','=',False)]),
400 'picking_id': fields.many2one('stock.picking', 'Packing list', readonly=True,
401 help="This is the internal picking list take bring the raw materials to the production plan."),
402 'move_prod_id': fields.many2one('stock.move', 'Move product', readonly=True),
403 'move_lines': fields.many2many('stock.move', 'mrp_production_move_ids', 'production_id', 'move_id', 'Products Consummed'),
405 'move_created_ids': fields.one2many('stock.move', 'production_id', 'Moves Created'),
406 'product_lines': fields.one2many('mrp.production.product.line', 'production_id', 'Scheduled goods'),
407 'workcenter_lines': fields.one2many('mrp.production.workcenter.line', 'production_id', 'Workcenters Utilisation'),
409 '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),
410 'sale_name': fields.function(_sale_name_calc, method=True, type='char', string='Sale Name'),
411 'sale_ref': fields.function(_sale_ref_calc, method=True, type='char', string='Sale Ref'),
414 'priority': lambda *a: '1',
415 'state': lambda *a: 'draft',
416 'date_planned': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
417 'product_qty': lambda *a: 1.0,
418 'name': lambda x,y,z,c: x.pool.get('ir.sequence').get(y,z,'mrp.production') or '/',
420 _order = 'date_planned asc, priority desc';
422 def location_id_change(self, cr, uid, ids, src, dest, context={}):
426 return {'value': {'location_dest_id': src}}
429 def product_id_change(self, cr, uid, ids, product):
432 res = self.pool.get('product.product').read(cr, uid, [product], ['uom_id'])[0]
433 uom = res['uom_id'] and res['uom_id'][0]
434 result = {'product_uom':uom}
435 return {'value':result}
437 def action_picking_except(self, cr, uid, ids):
438 self.write(cr, uid, ids, {'state':'picking_except'})
441 def action_compute(self, cr, uid, ids, properties=[]):
443 for production in self.browse(cr, uid, ids):
444 cr.execute('delete from mrp_production_product_line where production_id=%d', (production.id,))
445 cr.execute('delete from mrp_production_workcenter_line where production_id=%d', (production.id,))
446 bom_point = production.bom_id
447 bom_id = production.bom_id.id
449 bom_id = self.pool.get('mrp.bom')._bom_find(cr, uid, production.product_id.id, production.product_uom.id, properties)
451 self.write(cr, uid, [production.id], {'bom_id': bom_id})
452 bom_point = self.pool.get('mrp.bom').browse(cr, uid, [bom_id])[0]
455 raise osv.except_osv('Error', "Couldn't find bill of material for product")
457 #if bom_point.routing_id and bom_point.routing_id.location_id:
458 # self.write(cr, uid, [production.id], {'location_src_id': bom_point.routing_id.location_id.id})
460 factor = production.product_qty * production.product_uom.factor / bom_point.product_uom.factor
461 res = self.pool.get('mrp.bom')._bom_explode(cr, uid, bom_point, factor / bom_point.product_qty, properties)
465 line['production_id'] = production.id
466 self.pool.get('mrp.production.product.line').create(cr, uid, line)
467 for line in results2:
468 line['production_id'] = production.id
469 self.pool.get('mrp.production.workcenter.line').create(cr, uid, line)
472 def action_cancel(self, cr, uid, ids):
473 for production in self.browse(cr, uid, ids):
474 if production.move_created_ids:
475 self.pool.get('stock.move').action_cancel(cr, uid, [x.id for x in production.move_created_ids])
476 self.pool.get('stock.move').action_cancel(cr, uid, [x.id for x in production.move_lines])
477 self.write(cr, uid, ids, {'state':'cancel','move_lines':[(6,0,[])]})
480 #XXX: may be a bug here; lot_lines are unreserved for a few seconds;
481 # between the end of the picking list and the call to this function
482 def action_ready(self, cr, uid, ids):
483 self.write(cr, uid, ids, {'state':'ready'})
484 for production in self.browse(cr, uid, ids):
485 if production.move_prod_id:
486 self.pool.get('stock.move').write(cr, uid, [production.move_prod_id.id],
487 {'location_id':production.location_dest_id.id})
490 #TODO Review materials in function in_prod and prod_end.
491 def action_production_end(self, cr, uid, ids):
493 for production in self.browse(cr, uid, ids):
494 for res in production.move_lines:
495 for move in production.move_created_ids:
496 #XXX must use the orm
497 cr.execute('INSERT INTO stock_move_history_ids \
498 (parent_id, child_id) VALUES (%d,%d)',
500 move_ids.append(res.id)
501 if production.move_created_ids:
502 #TODO There we should handle the residus move creation
503 vals= {'state':'confirmed'}
504 new_moves = [x.id for x in production.move_created_ids]
505 self.pool.get('stock.move').write(cr, uid, new_moves, vals)
507 #XXX Why is it there ? Aren't we suppose to already have a created_move ?
508 source = production.product_id.product_tmpl_id.property_stock_production.id
510 'name':'PROD:'+production.name,
511 'date_planned': production.date_planned,
512 'product_id': production.product_id.id,
513 'product_qty': production.product_qty,
514 'product_uom': production.product_uom.id,
515 'product_uos_qty': production.product_uos and production.product_uos_qty or False,
516 'product_uos': production.product_uos and production.product_uos.id or False,
517 'location_id': source,
518 'location_dest_id': production.location_dest_id.id,
519 'move_dest_id': production.move_prod_id.id,
522 new_moves = [self.pool.get('stock.move').create(cr, uid, vals)]
523 self.write(cr, uid, [production.id],
524 {'move_created_ids': [(6, 'WTF', new_moves)]})
525 if not production.date_finnished:
526 self.write(cr, uid, [production.id],
527 {'date_finnished': time.strftime('%Y-%m-%d %H:%M:%S')})
528 self.pool.get('stock.move').check_assign(cr, uid, new_moves)
529 self.pool.get('stock.move').action_done(cr, uid, new_moves)
530 self._costs_generate(cr, uid, production)
531 self.pool.get('stock.move').action_done(cr, uid, move_ids)
532 self.write(cr, uid, ids, {'state': 'done'})
535 def _costs_generate(self, cr, uid, production):
537 for wc_line in production.workcenter_lines:
538 wc = wc_line.workcenter_id
539 if wc.costs_journal_id and wc.costs_general_account_id:
540 value = wc_line.hour * wc.costs_hour
541 account = wc.costs_hour_account_id.id
542 if value and account:
544 self.pool.get('account.analytic.line').create(cr, uid, {
545 'name': wc_line.name+' (H)',
547 'account_id': account,
548 'general_account_id': wc.costs_general_account_id.id,
549 'journal_id': wc.costs_journal_id.id,
552 if wc.costs_journal_id and wc.costs_general_account_id:
553 value = wc_line.cycle * wc.costs_cycle
554 account = wc.costs_cycle_account_id.id
555 if value and account:
557 self.pool.get('account.analytic.line').create(cr, uid, {
558 'name': wc_line.name+' (C)',
560 'account_id': account,
561 'general_account_id': wc.costs_general_account_id.id,
562 'journal_id': wc.costs_journal_id.id,
567 def action_in_production(self, cr, uid, ids):
569 for production in self.browse(cr, uid, ids):
570 for res in production.move_lines:
571 move_ids.append(res.id)
572 if not production.date_start:
573 self.write(cr, uid, [production.id],
574 {'date_start': time.strftime('%Y-%m-%d %H:%M:%S')})
575 self.pool.get('stock.move').action_done(cr, uid, move_ids)
576 self.write(cr, uid, ids, {'state': 'in_production'})
579 def test_if_product(self, cr, uid, ids):
581 for production in self.browse(cr, uid, ids):
582 if not production.product_lines:
583 if not self.action_compute(cr, uid, [production.id]):
587 def _get_auto_picking(self, cr, uid, production):
590 def action_confirm(self, cr, uid, ids):
592 for production in self.browse(cr, uid, ids):
593 if not production.product_lines:
594 self.action_compute(cr, uid, [production.id])
595 production = self.browse(cr, uid, [production.id])[0]
597 pick_type = 'internal'
599 if production.bom_id.routing_id and production.bom_id.routing_id.location_id:
600 routing_loc = production.bom_id.routing_id.location_id
601 if routing_loc.usage<>'internal':
603 address_id = routing_loc.address_id and routing_loc.address_id.id or False
604 routing_loc = routing_loc.id
605 picking_id = self.pool.get('stock.picking').create(cr, uid, {
606 'origin': (production.origin or '').split(':')[0] +':'+production.name,
610 'address_id': address_id,
611 'auto_picking': self._get_auto_picking(cr, uid, production),
615 source = production.product_id.product_tmpl_id.property_stock_production.id
617 'name':'PROD:'+production.name,
618 'date_planned': production.date_planned,
619 'product_id': production.product_id.id,
620 'product_qty': production.product_qty,
621 'product_uom': production.product_uom.id,
622 'product_uos_qty': production.product_uos and production.product_uos_qty or False,
623 'product_uos': production.product_uos and production.product_uos.id or False,
624 'location_id': source,
625 'location_dest_id': production.location_dest_id.id,
626 'move_dest_id': production.move_prod_id.id,
629 res_final_id = self.pool.get('stock.move').create(cr, uid, data)
631 self.write(cr, uid, [production.id], {'move_created_ids': [(6, 'WTF', [res_final_id])]})
633 for line in production.product_lines:
635 newdate = production.date_planned
636 if line.product_id.type in ('product', 'consu'):
637 res_dest_id = self.pool.get('stock.move').create(cr, uid, {
638 'name':'PROD:'+production.name,
639 'date_planned': production.date_planned,
640 'product_id': line.product_id.id,
641 'product_qty': line.product_qty,
642 'product_uom': line.product_uom.id,
643 'product_uos_qty': line.product_uos and line.product_uos_qty or False,
644 'product_uos': line.product_uos and line.product_uos.id or False,
645 'location_id': routing_loc or production.location_src_id.id,
646 'location_dest_id': source,
647 'move_dest_id': res_final_id,
650 moves.append(res_dest_id)
651 move_id = self.pool.get('stock.move').create(cr, uid, {
652 'name':'PROD:'+production.name,
653 'picking_id':picking_id,
654 'product_id': line.product_id.id,
655 'product_qty': line.product_qty,
656 'product_uom': line.product_uom.id,
657 'product_uos_qty': line.product_uos and line.product_uos_qty or False,
658 'product_uos': line.product_uos and line.product_uos.id or False,
659 'date_planned': newdate,
660 'move_dest_id': res_dest_id,
661 'location_id': production.location_src_id.id,
662 'location_dest_id': routing_loc or production.location_src_id.id,
665 proc_id = self.pool.get('mrp.procurement').create(cr, uid, {
666 'name': (production.origin or '').split(':')[0] + ':' + production.name,
667 'origin': (production.origin or '').split(':')[0] + ':' + production.name,
668 'date_planned': newdate,
669 'product_id': line.product_id.id,
670 'product_qty': line.product_qty,
671 'product_uom': line.product_uom.id,
672 'product_uos_qty': line.product_uos and line.product_qty or False,
673 'product_uos': line.product_uos and line.product_uos.id or False,
674 'location_id': production.location_src_id.id,
675 'procure_method': line.product_id.procure_method,
678 wf_service = netsvc.LocalService("workflow")
679 wf_service.trg_validate(uid, 'mrp.procurement', proc_id, 'button_confirm', cr)
681 wf_service = netsvc.LocalService("workflow")
682 wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
683 self.write(cr, uid, [production.id], {'picking_id':picking_id, 'move_lines': [(6,0,moves)], 'state':'confirmed'})
686 def force_production(self, cr, uid, ids, *args):
687 pick_obj = self.pool.get('stock.picking')
688 pick_obj.force_assign(cr, uid, [prod.picking_id.id for prod in self.browse(cr, uid, ids)])
694 class stock_move(osv.osv):
696 _inherit = 'stock.move'
698 'production_id': fields.many2one('mrp.production', 'Production', select=True),
702 class mrp_production_workcenter_line(osv.osv):
703 _name = 'mrp.production.workcenter.line'
704 _description = 'Production workcenters used'
706 'name': fields.char('Name', size=64, required=True),
707 'workcenter_id': fields.many2one('mrp.workcenter', 'Workcenter', required=True),
708 'cycle': fields.float('Nbr of cycle'),
709 'hour': fields.float('Nbr of hour'),
710 'sequence': fields.integer('Sequence', required=True),
711 'production_id': fields.many2one('mrp.production', 'Production Order', select=True),
714 'sequence': lambda *a: 1,
715 'hour': lambda *a: 0,
716 'cycle': lambda *a: 0,
718 mrp_production_workcenter_line()
720 class mrp_production_product_line(osv.osv):
721 _name = 'mrp.production.product.line'
722 _description = 'Production scheduled products'
724 'name': fields.char('Name', size=64, required=True),
725 'product_id': fields.many2one('product.product', 'Product', required=True),
726 'product_qty': fields.float('Product Qty', required=True),
727 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True),
728 'product_uos_qty': fields.float('Product UOS Qty'),
729 'product_uos': fields.many2one('product.uom', 'Product UOS'),
730 'production_id': fields.many2one('mrp.production', 'Production Order', select=True),
732 mrp_production_product_line()
734 # ------------------------------------------------------------------
736 # ------------------------------------------------------------------
738 # Produce, Buy or Find products and place a move
739 # then wizard for picking lists & move
741 class mrp_procurement(osv.osv):
742 _name = "mrp.procurement"
743 _description = "Procurement"
745 'name': fields.char('Name', size=64, required=True),
746 'origin': fields.char('Origin', size=64,
747 help="Reference of the document that created this procurement.\n"
748 "This is automatically completed by Open ERP."),
749 'priority': fields.selection([('0','Not urgent'),('1','Normal'),('2','Urgent'),('3','Very Urgent')], 'Priority', required=True),
750 'date_planned': fields.datetime('Scheduled date', required=True),
751 'date_close': fields.datetime('Date Closed'),
752 'product_id': fields.many2one('product.product', 'Product', required=True),
753 'product_qty': fields.float('Quantity', required=True),
754 'product_uom': fields.many2one('product.uom', 'Product UoM', required=True),
755 'product_uos_qty': fields.float('UoS Quantity'),
756 'product_uos': fields.many2one('product.uom', 'Product UoS'),
757 'move_id': fields.many2one('stock.move', 'Reservation', ondelete='set null'),
759 'bom_id': fields.many2one('mrp.bom', 'BoM', ondelete='cascade', select=True),
761 'close_move': fields.boolean('Close Move at end', required=True),
762 'location_id': fields.many2one('stock.location', 'Location', required=True),
763 'procure_method': fields.selection([('make_to_stock','from stock'),('make_to_order','on order')], 'Procurement Method', states={'draft':[('readonly',False)], 'confirmed':[('readonly',False)]},
764 readonly=True, required=True, help="If you encode manually a procurement, you probably want to use" \
765 " a make to order method."),
767 'purchase_id': fields.many2one('purchase.order', 'Purchase Order'),
768 'purchase_line_id': fields.many2one('purchase.order.line', 'Purchase Order Line'),
770 'property_ids': fields.many2many('mrp.property', 'mrp_procurement_property_rel', 'procurement_id','property_id', 'Properties'),
772 'message': fields.char('Latest error', size=64),
773 'state': fields.selection([('draft','Draft'),('confirmed','Confirmed'),('exception','Exception'),('running','Running'),('cancel','Cancel'),('done','Done'),('waiting','Waiting')], 'Status')
776 'state': lambda *a: 'draft',
777 'priority': lambda *a: '1',
778 'date_planned': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
779 'close_move': lambda *a: 0,
780 'procure_method': lambda *a: 'make_to_order',
782 def onchange_product_id(self, cr, uid, ids, product_id, context={}):
784 w=self.pool.get('product.product').browse(cr,uid,product_id, context)
786 'product_uom':w.uom_id.id,
787 'product_uos':w.uos_id and w.uos_id.id or w.uom_id.id
792 def check_product(self, cr, uid, ids):
793 for procurement in self.browse(cr, uid, ids):
794 if procurement.product_id.type in ('product', 'consu'):
798 def check_move_cancel(self, cr, uid, ids, context={}):
800 for procurement in self.browse(cr, uid, ids, context):
801 if procurement.move_id:
802 if not procurement.move_id.state=='cancel':
806 def check_move_done(self, cr, uid, ids, context={}):
808 for proc in self.browse(cr, uid, ids, context):
810 if not proc.move_id.state=='done':
815 # This method may be overrided by objects that override mrp.procurment
816 # for computing their own purpose
818 def _quantity_compute_get(self, cr, uid, proc, context={}):
819 if proc.product_id.type=='product':
820 return proc.move_id.product_uos_qty
823 def _uom_compute_get(self, cr, uid, proc, context={}):
824 if proc.product_id.type=='product':
825 if proc.move_id.product_uos:
826 return proc.move_id.product_uos.id
830 # Return the quantity of product shipped/produced/served, wich may be
831 # different from the planned quantity
833 def quantity_get(self, cr, uid, id, context={}):
834 proc = self.browse(cr, uid, id, context)
835 result = self._quantity_compute_get(cr, uid, proc, context)
837 result = proc.product_qty
840 def uom_get(self, cr, uid, id, context=None):
841 proc = self.browse(cr, uid, id, context)
842 result = self._uom_compute_get(cr, uid, proc, context)
844 result = proc.product_uom.id
847 def check_waiting(self, cr, uid, ids, context=[]):
848 for procurement in self.browse(cr, uid, ids, context=context):
849 if procurement.move_id and procurement.move_id.state=='auto':
853 def check_produce_service(self, cr, uid, procurement, context=[]):
856 def check_produce_product(self, cr, uid, procurement, context=[]):
857 properties = [x.id for x in procurement.property_ids]
858 bom_id = self.pool.get('mrp.bom')._bom_find(cr, uid, procurement.product_id.id, procurement.product_uom.id, properties)
860 cr.execute('update mrp_procurement set message=%s where id=%d', ('No BoM defined for this product !', procurement.id))
864 def check_make_to_stock(self, cr, uid, ids, context={}):
866 for procurement in self.browse(cr, uid, ids, context=context):
867 if procurement.product_id.type=='service':
868 ok = ok and self._check_make_to_stock_service(cr, uid, procurement, context)
870 ok = ok and self._check_make_to_stock_product(cr, uid, procurement, context)
873 def check_produce(self, cr, uid, ids, context={}):
875 user = self.pool.get('res.users').browse(cr, uid, uid)
876 for procurement in self.browse(cr, uid, ids):
877 if procurement.product_id.product_tmpl_id.supply_method=='buy':
878 if procurement.product_id.seller_ids:
879 partner = procurement.product_id.seller_ids[0].name
880 if user.company_id and user.company_id.partner_id:
881 if partner.id == user.company_id.partner_id.id:
884 if procurement.product_id.product_tmpl_id.type=='service':
885 res = res and self.check_produce_service(cr, uid, procurement, context)
887 res = res and self.check_produce_product(cr, uid, procurement, context)
892 def check_buy(self, cr, uid, ids):
893 user = self.pool.get('res.users').browse(cr, uid, uid)
894 for procurement in self.browse(cr, uid, ids):
895 if procurement.product_id.product_tmpl_id.supply_method=='produce':
897 if not procurement.product_id.seller_ids:
898 cr.execute('update mrp_procurement set message=%s where id=%d', ('No supplier defined for this product !', procurement.id))
900 partner = procurement.product_id.seller_ids[0].name
901 if user.company_id and user.company_id.partner_id:
902 if partner.id == user.company_id.partner_id.id:
904 address_id = self.pool.get('res.partner').address_get(cr, uid, [partner.id], ['delivery'])['delivery']
906 cr.execute('update mrp_procurement set message=%s where id=%d', ('No address defined for the supplier', procurement.id))
910 def test_cancel(self, cr, uid, ids):
911 for record in self.browse(cr, uid, ids):
912 if record.move_id and record.move_id.state=='cancel':
916 def action_confirm(self, cr, uid, ids, context={}):
917 for procurement in self.browse(cr, uid, ids):
918 if procurement.product_id.type in ('product', 'consu'):
919 if not procurement.move_id:
920 source = procurement.location_id.id
921 if procurement.procure_method=='make_to_order':
922 source = procurement.product_id.product_tmpl_id.property_stock_procurement.id
923 id = self.pool.get('stock.move').create(cr, uid, {
924 'name': 'PROC:'+procurement.name,
925 'location_id': source,
926 'location_dest_id': procurement.location_id.id,
927 'product_id': procurement.product_id.id,
928 'product_qty':procurement.product_qty,
929 'product_uom': procurement.product_uom.id,
930 'date_planned': procurement.date_planned,
933 self.write(cr, uid, [procurement.id], {'move_id': id, 'close_move':1})
936 if procurement.procure_method=='make_to_stock' and procurement.move_id.state in ('waiting',):
937 id = self.pool.get('stock.move').write(cr, uid, [procurement.move_id.id], {'state':'confirmed'})
938 self.write(cr, uid, ids, {'state':'confirmed','message':''})
941 def action_move_assigned(self, cr, uid, ids):
942 self.write(cr, uid, ids, {'state':'running','message':'from stock: products assigned.'})
945 def _check_make_to_stock_service(self, cr, uid, procurement, context={}):
948 def _check_make_to_stock_product(self, cr, uid, procurement, context={}):
950 if procurement.move_id:
951 id = procurement.move_id.id
952 if not (procurement.move_id.state in ('done','assigned','cancel')):
953 ok = ok and self.pool.get('stock.move').action_assign(cr, uid, [id])
954 cr.execute('select count(id) from stock_warehouse_orderpoint where product_id=%d', (procurement.product_id.id,))
955 if not cr.fetchone()[0]:
956 cr.execute('update mrp_procurement set message=%s where id=%d', ('from stock and no minimum orderpoint rule defined', procurement.id))
959 def action_produce_assign_service(self, cr, uid, ids, context={}):
960 for procurement in self.browse(cr, uid, ids):
961 self.write(cr, uid, [procurement.id], {'state':'running'})
964 def action_produce_assign_product(self, cr, uid, ids, context={}):
966 company = self.pool.get('res.users').browse(cr, uid, uid, context).company_id
967 for procurement in self.browse(cr, uid, ids):
968 res_id = procurement.move_id.id
969 loc_id = procurement.location_id.id
970 newdate = DateTime.strptime(procurement.date_planned, '%Y-%m-%d %H:%M:%S') - DateTime.RelativeDateTime(days=procurement.product_id.product_tmpl_id.produce_delay or 0.0)
971 newdate = newdate - DateTime.RelativeDateTime(days=company.manufacturing_lead)
972 produce_id = self.pool.get('mrp.production').create(cr, uid, {
973 'origin': procurement.origin,
974 'product_id': procurement.product_id.id,
975 'product_qty': procurement.product_qty,
976 'product_uom': procurement.product_uom.id,
977 'product_uos_qty': procurement.product_uos and procurement.product_uos_qty or False,
978 'product_uos': procurement.product_uos and procurement.product_uos.id or False,
979 'location_src_id': procurement.location_id.id,
980 'location_dest_id': procurement.location_id.id,
981 'bom_id': procurement.bom_id and procurement.bom_id.id or False,
982 'date_planned': newdate,
983 'move_prod_id': res_id,
985 self.write(cr, uid, [procurement.id], {'state':'running'})
986 bom_result = self.pool.get('mrp.production').action_compute(cr, uid,
987 [produce_id], properties=[x.id for x in procurement.property_ids])
988 wf_service = netsvc.LocalService("workflow")
989 wf_service.trg_validate(uid, 'mrp.production', produce_id, 'button_confirm', cr)
992 def action_po_assign(self, cr, uid, ids, context={}):
994 company = self.pool.get('res.users').browse(cr, uid, uid, context).company_id
995 for procurement in self.browse(cr, uid, ids):
996 res_id = procurement.move_id.id
997 partner = procurement.product_id.seller_ids[0].name
998 partner_id = partner.id
999 address_id = self.pool.get('res.partner').address_get(cr, uid, [partner_id], ['delivery'])['delivery']
1000 pricelist_id = partner.property_product_pricelist_purchase.id
1002 uom_id = procurement.product_id.uom_po_id.id
1004 qty = self.pool.get('product.uom')._compute_qty(cr, uid, procurement.product_uom.id, procurement.product_qty, uom_id)
1005 if procurement.product_id.seller_ids[0].qty:
1006 qty=max(qty,procurement.product_id.seller_ids[0].qty)
1008 price = self.pool.get('product.pricelist').price_get(cr, uid, [pricelist_id], procurement.product_id.id, qty, False, {'uom': uom_id})[pricelist_id]
1010 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)
1011 newdate = newdate - DateTime.RelativeDateTime(days=company.po_lead)
1012 context.update({'lang':partner.lang})
1013 product=self.pool.get('product.product').browse(cr,uid,procurement.product_id.id,context=context)
1016 'name': product.name,
1018 'product_id': procurement.product_id.id,
1019 'product_uom': uom_id,
1020 'price_unit': price,
1021 'date_planned': newdate.strftime('%Y-%m-%d %H:%M:%S'),
1022 'move_dest_id': res_id,
1023 'notes':product.description_purchase,
1026 taxes_ids = procurement.product_id.product_tmpl_id.supplier_taxes_id
1027 self.pool.get('account.fiscal.position').map_tax(cr, uid, partner, taxes_ids)
1029 'taxes_id':[(6,0,taxes_ids)]
1031 purchase_id = self.pool.get('purchase.order').create(cr, uid, {
1032 'origin': procurement.origin,
1033 'partner_id': partner_id,
1034 'partner_address_id': address_id,
1035 'location_id': procurement.location_id.id,
1036 'pricelist_id': pricelist_id,
1037 'order_line': [(0,0,line)]
1039 self.write(cr, uid, [procurement.id], {'state':'running', 'purchase_id':purchase_id})
1042 def action_cancel(self, cr, uid, ids):
1044 for proc in self.browse(cr, uid, ids):
1046 todo.append(proc.move_id.id)
1048 self.pool.get('stock.move').action_cancel(cr, uid, [proc.move_id.id])
1049 self.write(cr, uid, ids, {'state':'cancel'})
1051 wf_service = netsvc.LocalService("workflow")
1053 wf_service.trg_trigger(uid, 'mrp.procurement', id, cr)
1057 def action_check_finnished(self, cr, uid, ids):
1060 def action_check(self, cr, uid, ids):
1062 for procurement in self.browse(cr, uid, ids):
1063 if procurement.move_id.state=='assigned' or procurement.move_id.state=='done':
1064 self.action_done(cr, uid, [procurement.id])
1068 def action_done(self, cr, uid, ids):
1069 for procurement in self.browse(cr, uid, ids):
1070 if procurement.move_id:
1071 if procurement.close_move and (procurement.move_id.state <> 'done'):
1072 self.pool.get('stock.move').action_done(cr, uid, [procurement.move_id.id])
1073 res = self.write(cr, uid, ids, {'state':'done', 'date_close':time.strftime('%Y-%m-%d')})
1075 wf_service = netsvc.LocalService("workflow")
1077 wf_service.trg_trigger(uid, 'mrp.procurement', id, cr)
1079 def run_scheduler(self, cr, uid, automatic=False, use_new_cursor=False, context=None):
1081 use_new_cursor: False or the dbname
1085 self._procure_confirm(cr, uid, use_new_cursor=use_new_cursor, context=context)
1086 self._procure_orderpoint_confirm(cr, uid, automatic=automatic,\
1087 use_new_cursor=use_new_cursor, context=context)
1091 class stock_warehouse_orderpoint(osv.osv):
1092 _name = "stock.warehouse.orderpoint"
1093 _description = "Orderpoint minimum rule"
1095 'name': fields.char('Name', size=32, required=True),
1096 'active': fields.boolean('Active'),
1097 'logic': fields.selection([('max','Order to Max'),('price','Best price (not yet active!)')], 'Reordering Mode', required=True),
1098 'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse', required=True),
1099 'location_id': fields.many2one('stock.location', 'Location', required=True),
1100 'product_id': fields.many2one('product.product', 'Product', required=True, domain=[('type','=','product')]),
1101 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True ),
1102 'product_min_qty': fields.float('Min Quantity', required=True,
1103 help="When the virtual stock goes belong the Min Quantity, Open ERP generates "\
1104 "a procurement to bring the virtual stock to the Max Quantity."),
1105 'product_max_qty': fields.float('Max Quantity', required=True,
1106 help="When the virtual stock goes belong the Min Quantity, Open ERP generates "\
1107 "a procurement to bring the virtual stock to the Max Quantity."),
1108 'qty_multiple': fields.integer('Qty Multiple', required=True,
1109 help="The procurement quantity will by rounded up to this multiple."),
1110 'procurement_id': fields.many2one('mrp.procurement', 'Purchase Order')
1113 'active': lambda *a: 1,
1114 'logic': lambda *a: 'max',
1115 'qty_multiple': lambda *a: 1,
1116 'name': lambda x,y,z,c: x.pool.get('ir.sequence').get(y,z,'mrp.warehouse.orderpoint') or '',
1117 'product_uom': lambda sel, cr, uid, context: context.get('product_uom', False),
1119 def onchange_warehouse_id(self, cr, uid, ids, warehouse_id, context={}):
1121 w=self.pool.get('stock.warehouse').browse(cr,uid,warehouse_id, context)
1122 v = {'location_id':w.lot_stock_id.id}
1125 def onchange_product_id(self, cr, uid, ids, product_id, context={}):
1127 prod=self.pool.get('product.product').browse(cr,uid,product_id)
1128 v = {'product_uom':prod.uom_id.id}
1131 stock_warehouse_orderpoint()
1134 class StockMove(osv.osv):
1135 _inherit = 'stock.move'
1137 'procurements': fields.one2many('mrp.procurement', 'move_id', 'Procurements'),
1139 def _action_explode(self, cr, uid, move, context={}):
1140 if move.product_id.supply_method=='produce' and move.product_id.procure_method=='make_to_order':
1141 bis = self.pool.get('mrp.bom').search(cr, uid, [
1142 ('product_id','=',move.product_id.id),
1143 ('bom_id','=',False),
1144 ('type','=','phantom')])
1146 factor = move.product_qty
1147 bom_point = self.pool.get('mrp.bom').browse(cr, uid, bis[0])
1148 res = self.pool.get('mrp.bom')._bom_explode(cr, uid, bom_point, factor, [])
1149 dest = move.product_id.product_tmpl_id.property_stock_production.id
1151 if move.state=='assigned':
1155 'picking_id': move.picking_id.id,
1156 'product_id': line['product_id'],
1157 'product_uom': line['product_uom'],
1158 'product_qty': line['product_qty'],
1159 'product_uos': line['product_uos'],
1160 'product_uos_qty': line['product_uos_qty'],
1161 'move_dest_id': move.id,
1163 'location_dest_id': dest,
1164 'move_history_ids': [(6,0,[move.id])],
1165 'move_history_ids2': [(6,0,[])],
1168 mid = self.pool.get('stock.move').copy(cr, uid, move.id, default=valdef)
1169 prodobj = self.pool.get('product.product').browse(cr, uid, line['product_id'], context=context)
1170 proc_id = self.pool.get('mrp.procurement').create(cr, uid, {
1171 'name': (move.picking_id.origin or ''),
1172 'origin': (move.picking_id.origin or ''),
1173 'date_planned': move.date_planned,
1174 'product_id': line['product_id'],
1175 'product_qty': line['product_qty'],
1176 'product_uom': line['product_uom'],
1177 'product_uos_qty': line['product_uos'] and line['product_uos_qty'] or False,
1178 'product_uos': line['product_uos'],
1179 'location_id': move.location_id.id,
1180 'procure_method': prodobj.procure_method,
1183 wf_service = netsvc.LocalService("workflow")
1184 wf_service.trg_validate(uid, 'mrp.procurement', proc_id, 'button_confirm', cr)
1185 self.pool.get('stock.move').write(cr, uid, [move.id], {
1186 'location_id': move.location_dest_id.id,
1187 'auto_validate': True,
1188 'picking_id': False,
1189 'location_id': dest,
1192 for m in self.pool.get('mrp.procurement').search(cr, uid, [('move_id','=',move.id)], context):
1193 wf_service = netsvc.LocalService("workflow")
1194 wf_service.trg_validate(uid, 'mrp.procurement', m, 'button_wait_done', cr)
1199 class StockPicking(osv.osv):
1200 _inherit = 'stock.picking'
1202 def test_finnished(self, cursor, user, ids):
1203 wf_service = netsvc.LocalService("workflow")
1204 res = super(StockPicking, self).test_finnished(cursor, user, ids)
1205 for picking in self.browse(cursor, user, ids):
1206 for move in picking.move_lines:
1207 if move.state == 'done' and move.procurements:
1208 for procurement in move.procurements:
1209 wf_service.trg_validate(user, 'mrp.procurement',
1210 procurement.id, 'button_check', cursor)
1214 # Explode picking by replacing phantom BoMs
1216 def action_explode(self, cr, uid, picks, *args):
1218 self.pool.get('stock.move')._action_explode(cr, uid, move)
1223 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: