1 # -*- encoding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2008 Tiny SPRL (<http://tiny.be>). All Rights Reserved
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 ##############################################################################
23 from osv import fields
29 from mx import DateTime
31 #----------------------------------------------------------
33 #----------------------------------------------------------
34 # capacity_hour : capacity per hour. default: 1.0.
35 # Eg: If 5 concurrent operations at one time: capacity = 5 (because 5 employees)
36 # unit_per_cycle : how many units are produced for one cycle
38 # TODO: Work Center may be recursive ?
40 class mrp_workcenter(osv.osv):
41 _name = 'mrp.workcenter'
42 _description = 'Workcenter'
44 'name': fields.char('Workcenter Name', size=64, required=True),
45 'active': fields.boolean('Active'),
46 'type': fields.selection([('machine','Machine'),('hr','Human Resource'),('tool','Tool')], 'Type', required=True),
47 'code': fields.char('Code', size=16),
48 'timesheet_id': fields.many2one('hr.timesheet.group', 'Working Time', help="The normal working time of the workcenter."),
49 'note': fields.text('Description', help="Description of the workcenter. Explain here what's a cycle according to this workcenter."),
51 '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."),
53 'time_cycle': fields.float('Time for 1 cycle (hour)', help="Time in hours for doing one cycle."),
54 'time_start': fields.float('Time before prod.', help="Time in hours for the setup."),
55 'time_stop': fields.float('Time after prod.', help="Time in hours for the cleaning."),
56 'time_efficiency': fields.float('Time Efficiency', help="Factor that multiplies all times expressed in the workcenter."),
58 'costs_hour': fields.float('Cost per hour'),
59 'costs_hour_account_id': fields.many2one('account.analytic.account', 'Hour Account', domain=[('type','<>','view')],
60 help="Complete this only if you want automatic analytic accounting entries on production orders."),
61 'costs_cycle': fields.float('Cost per cycle'),
62 'costs_cycle_account_id': fields.many2one('account.analytic.account', 'Cycle Account', domain=[('type','<>','view')],
63 help="Complete this only if you want automatic analytic accounting entries on production orders."),
64 'costs_journal_id': fields.many2one('account.analytic.journal', 'Analytic Journal'),
65 'costs_general_account_id': fields.many2one('account.account', 'General Account', domain=[('type','<>','view')]),
68 'active': lambda *a: 1,
69 'type': lambda *a: 'machine',
70 'time_efficiency': lambda *a: 1.0,
71 'capacity_per_cycle': lambda *a: 1.0,
76 class mrp_property_group(osv.osv):
77 _name = 'mrp.property.group'
78 _description = 'Property Group'
80 'name': fields.char('Property Group', size=64, required=True),
81 'description': fields.text('Description'),
85 class mrp_property(osv.osv):
86 _name = 'mrp.property'
87 _description = 'Property'
89 'name': fields.char('Name', size=64, required=True),
90 'composition': fields.selection([('min','min'),('max','max'),('plus','plus')], 'Properties composition', required=True, help="Not used in computations, for information purpose only."),
91 'group_id': fields.many2one('mrp.property.group', 'Property Group', required=True),
92 'description': fields.text('Description'),
95 'composition': lambda *a: 'min',
99 class mrp_routing(osv.osv):
100 _name = 'mrp.routing'
101 _description = 'Routing'
103 'name': fields.char('Name', size=64, required=True),
104 'active': fields.boolean('Active'),
105 'code': fields.char('Code', size=8),
107 'note': fields.text('Description'),
108 'workcenter_lines': fields.one2many('mrp.routing.workcenter', 'routing_id', 'Workcenters'),
110 'location_id': fields.many2one('stock.location', 'Production Location',
111 help="Keep empty if you produce at the location where the finnished products are needed." \
112 "Put a location if you produce at a fixed location. This can be a partner location " \
113 "if you subcontract the manufacturing operations."
117 'active': lambda *a: 1,
121 class mrp_routing_workcenter(osv.osv):
122 _name = 'mrp.routing.workcenter'
123 _description = 'Routing workcenter usage'
125 'workcenter_id': fields.many2one('mrp.workcenter', 'Workcenter', required=True),
126 'name': fields.char('Name', size=64, required=True),
127 'sequence': fields.integer('Sequence'),
128 'cycle_nbr': fields.float('Number of Cycle', required=True,
129 help="A cycle is defined in the workcenter definition."),
130 'hour_nbr': fields.float('Number of Hours', required=True),
131 'routing_id': fields.many2one('mrp.routing', 'Parent Routing', select=True),
132 'note': fields.text('Description')
135 'cycle_nbr': lambda *a: 1.0,
136 'hour_nbr': lambda *a: 0.0,
138 mrp_routing_workcenter()
140 class mrp_bom(osv.osv):
142 _description = 'Bill of Material'
143 def _child_compute(self, cr, uid, ids, name, arg, context={}):
145 for bom in self.browse(cr, uid, ids, context=context):
146 result[bom.id] = map(lambda x: x.id, bom.bom_lines)
147 ok = ((name=='child_complete_ids') and (bom.product_id.supply_method=='produce'))
148 if bom.type=='phantom' or ok:
149 sids = self.pool.get('mrp.bom').search(cr, uid, [('bom_id','=',False),('product_id','=',bom.product_id.id)])
151 bom2 = self.pool.get('mrp.bom').browse(cr, uid, sids[0], context=context)
152 result[bom.id] += map(lambda x: x.id, bom2.bom_lines)
154 def _compute_type(self, cr, uid, ids, field_name, arg, context):
155 res = dict(map(lambda x: (x,''), ids))
156 for line in self.browse(cr, uid, ids):
157 if line.type=='phantom' and not line.bom_id:
160 if line.bom_lines or line.type=='phantom':
162 if line.product_id.supply_method=='produce':
163 if line.product_id.procure_method=='make_to_stock':
164 res[line.id] = 'stock'
166 res[line.id] = 'order'
169 'name': fields.char('Name', size=64, required=True),
170 'code': fields.char('Code', size=16),
171 'active': fields.boolean('Active'),
172 'type': fields.selection([('normal','Normal BoM'),('phantom','Sets / Phantom')], 'BoM Type', required=True, help=
173 "Use a phantom bill of material in raw materials lines that have to be " \
174 "automatically computed in on eproduction order and not one per level." \
175 "If you put \"Phantom/Set\" at the root level of a bill of material " \
176 "it is considered as a set or pack: the products are replaced by the components " \
177 "between the sale order to the picking without going through the production order." \
178 "The normal BoM will generate one production order per BoM level."),
179 'method': fields.function(_compute_type, string='Method', method=True, type='selection', selection=[('',''),('stock','On Stock'),('order','On Order'),('set','Set / Pack')]),
180 'date_start': fields.date('Valid From', help="Validity of this BoM or component. Keep empty if it's always valid."),
181 'date_stop': fields.date('Valid Until', help="Validity of this BoM or component. Keep empty if it's always valid."),
182 'sequence': fields.integer('Sequence'),
183 'position': fields.char('Internal Ref.', size=64, help="Reference to a position in an external plan."),
184 'product_id': fields.many2one('product.product', 'Product', required=True),
185 'product_uos_qty': fields.float('Product UOS Qty'),
186 'product_uos': fields.many2one('product.uom', 'Product UOS'),
187 'product_qty': fields.float('Product Qty', required=True),
188 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True),
189 'product_rounding': fields.float('Product Rounding', help="Rounding applied on the product quantity. For integer only values, put 1.0"),
190 '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."),
191 'bom_lines': fields.one2many('mrp.bom', 'bom_id', 'BoM Lines'),
192 'bom_id': fields.many2one('mrp.bom', 'Parent BoM', ondelete='cascade', select=True),
193 '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."),
194 'property_ids': fields.many2many('mrp.property', 'mrp_bom_property_rel', 'bom_id','property_id', 'Properties'),
195 'revision_ids': fields.one2many('mrp.bom.revision', 'bom_id', 'BoM Revisions'),
196 'revision_type': fields.selection([('numeric','numeric indices'),('alpha','alphabetical indices')], 'indice type'),
197 'child_ids': fields.function(_child_compute,relation='mrp.bom', method=True, string="BoM Hyerarchy", type='many2many'),
198 'child_complete_ids': fields.function(_child_compute,relation='mrp.bom', method=True, string="BoM Hyerarchy", type='many2many')
201 'active': lambda *a: 1,
202 'product_efficiency': lambda *a: 1.0,
203 'product_qty': lambda *a: 1.0,
204 'product_rounding': lambda *a: 1.0,
205 'type': lambda *a: 'normal',
209 ('bom_qty_zero', 'CHECK (product_qty>0)', 'All product quantities must be greater than 0.\n' \
210 'You should install the mrp_subproduct module if you want to manage extra products on BoMs !'),
213 def _check_recursion(self, cr, uid, ids):
216 cr.execute('select distinct bom_id from mrp_bom where id in ('+','.join(map(str,ids))+')')
217 ids = filter(None, map(lambda x:x[0], cr.fetchall()))
223 (_check_recursion, 'Error ! You can not create recursive BoM.', ['parent_id'])
227 def onchange_product_id(self, cr, uid, ids, product_id, name, context={}):
229 prod=self.pool.get('product.product').browse(cr,uid,[product_id])[0]
230 v = {'product_uom':prod.uom_id.id}
232 v['name'] = prod.name
236 def _bom_find(self, cr, uid, product_id, product_uom, properties = []):
238 # Why searching on BoM without parent ?
239 cr.execute('select id from mrp_bom where product_id=%d and bom_id is null order by sequence', (product_id,))
240 ids = map(lambda x: x[0], cr.fetchall())
243 for bom in self.pool.get('mrp.bom').browse(cr, uid, ids):
245 for prop_id in bom.property_ids:
246 if prop_id.id in properties:
248 if (prop>max_prop) or ((max_prop==0) and not result):
252 def _bom_explode(self, cr, uid, bom, factor, properties, addthis=False, level=10):
253 factor = factor / (bom.product_efficiency or 1.0)
254 factor = rounding(factor, bom.product_rounding)
255 if factor<bom.product_rounding:
256 factor = bom.product_rounding
259 if bom.type=='phantom' and not bom.bom_lines:
260 newbom = self._bom_find(cr, uid, bom.product_id.id, bom.product_uom.id, properties)
262 res = self._bom_explode(cr, uid, self.browse(cr, uid, [newbom])[0], factor*bom.product_qty, properties, addthis=True, level=level+10)
263 result = result + res[0]
264 result2 = result2 + res[1]
268 if addthis and not bom.bom_lines:
271 'name': bom.product_id.name,
272 'product_id': bom.product_id.id,
273 'product_qty': bom.product_qty * factor,
274 'product_uom': bom.product_uom.id,
275 'product_uos_qty': bom.product_uos and bom.product_uos_qty * factor or False,
276 'product_uos': bom.product_uos and bom.product_uos.id or False,
279 for wc_use in bom.routing_id.workcenter_lines:
280 wc = wc_use.workcenter_id
281 d, m = divmod(factor, wc_use.workcenter_id.capacity_per_cycle)
282 cycle = (d + (m and 1.0 or 0.0)) * wc_use.cycle_nbr
284 'name': bom.routing_id.name,
285 'workcenter_id': wc.id,
288 'hour': wc_use.hour_nbr + (wc.time_start+wc.time_stop+cycle*wc.time_cycle) * (wc.time_efficiency or 1.0),
290 for bom2 in bom.bom_lines:
291 res = self._bom_explode(cr, uid, bom2, factor, properties, addthis=True, level=level+10)
292 result = result + res[0]
293 result2 = result2 + res[1]
294 return result, result2
296 def set_indices(self, cr, uid, ids, context = {}):
297 if not ids or (ids and not ids[0]):
299 res = self.read(cr, uid, ids, ['revision_ids', 'revision_type'])
300 rev_ids = res[0]['revision_ids']
303 for rev_id in rev_ids:
304 if res[0]['revision_type'] == 'numeric':
305 self.pool.get('mrp.bom.revision').write(cr, uid, [rev_id], {'indice' : idx})
307 self.pool.get('mrp.bom.revision').write(cr, uid, [rev_id], {'indice' : "%c"%(idx+96,)})
313 class mrp_bom_revision(osv.osv):
314 _name = 'mrp.bom.revision'
315 _description = 'Bill of material revisions'
317 'name': fields.char('Modification name', size=64, required=True),
318 'description': fields.text('Description'),
319 'date': fields.date('Modification Date'),
320 'indice': fields.char('Revision', size=16),
321 'last_indice': fields.char('last indice', size=64),
322 'author_id': fields.many2one('res.users', 'Author'),
323 'bom_id': fields.many2one('mrp.bom', 'BoM', select=True),
327 'author_id': lambda x,y,z,c: z,
328 'date': lambda *a: time.strftime('%Y-%m-%d'),
336 return round(f / r) * r
338 class mrp_production(osv.osv):
339 _name = 'mrp.production'
340 _description = 'Production'
341 _date_name = 'date_planned'
343 def _get_sale_order(self,cr,uid,ids,field_name=False):
344 move_obj=self.pool.get('stock.move')
345 def get_parent_move(move_id):
346 move = move_obj.browse(cr,uid,move_id)
347 if move.move_dest_id:
348 return get_parent_move(move.move_dest_id.id)
350 productions=self.read(cr,uid,ids,['id','move_prod_id'])
352 for production in productions:
353 res[production['id']]=False
354 if production.get('move_prod_id',False):
355 parent_move_line=get_parent_move(production['move_prod_id'][0])
357 move = move_obj.browse(cr,uid,parent_move_line)
358 if field_name=='name':
359 res[production['id']]=move.sale_line_id and move.sale_line_id.order_id.name or False
360 if field_name=='client_order_ref':
361 res[production['id']]=move.sale_line_id and move.sale_line_id.order_id.client_order_ref or False
364 def _sale_name_calc(self, cr, uid, ids, prop, unknow_none, unknow_dict):
365 return self._get_sale_order(cr,uid,ids,field_name='name')
367 def _sale_ref_calc(self, cr, uid, ids, prop, unknow_none, unknow_dict):
368 return self._get_sale_order(cr,uid,ids,field_name='client_order_ref')
371 'name': fields.char('Reference', size=64, required=True),
372 'origin': fields.char('Origin', size=64),
373 'priority': fields.selection([('0','Not urgent'),('1','Normal'),('2','Urgent'),('3','Very Urgent')], 'Priority'),
375 'product_id': fields.many2one('product.product', 'Product', required=True, domain=[('type','<>','service')]),
376 'product_qty': fields.float('Product Qty', required=True),
377 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True),
378 'product_uos_qty': fields.float('Product Qty'),
379 'product_uos': fields.many2one('product.uom', 'Product UOM'),
381 'location_src_id': fields.many2one('stock.location', 'Raw Products Location', required=True,
382 help="Location where the system will look for products used in raw materials."),
383 'location_dest_id': fields.many2one('stock.location', 'Finnished Products Location', required=True,
384 help="Location where the system will stock the finnished products."),
386 'date_planned': fields.datetime('Scheduled date', required=True, select=1),
387 'date_start': fields.datetime('Start Date'),
388 'date_finnished': fields.datetime('End Date'),
390 'bom_id': fields.many2one('mrp.bom', 'Bill of Material', domain=[('bom_id','=',False)]),
392 'picking_id': fields.many2one('stock.picking', 'Packing list', readonly=True,
393 help="This is the internal picking list take bring the raw materials to the production plan."),
394 'move_prod_id': fields.many2one('stock.move', 'Move product', readonly=True),
395 'move_lines': fields.many2many('stock.move', 'mrp_production_move_ids', 'production_id', 'move_id', 'Products Consummed'),
397 'move_created_ids': fields.one2many('stock.move', 'production_id', 'Moves Created'),
398 'product_lines': fields.one2many('mrp.production.product.line', 'production_id', 'Scheduled goods'),
399 'workcenter_lines': fields.one2many('mrp.production.workcenter.line', 'production_id', 'Workcenters Utilisation'),
401 '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),
402 'sale_name': fields.function(_sale_name_calc, method=True, type='char', string='Sale Name'),
403 'sale_ref': fields.function(_sale_ref_calc, method=True, type='char', string='Sale Ref'),
406 'priority': lambda *a: '1',
407 'state': lambda *a: 'draft',
408 'date_planned': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
409 'product_qty': lambda *a: 1.0,
410 'name': lambda x,y,z,c: x.pool.get('ir.sequence').get(y,z,'mrp.production') or '/',
412 _order = 'date_planned asc, priority desc';
414 def location_id_change(self, cr, uid, ids, src, dest, context={}):
418 return {'value': {'location_dest_id': src}}
421 def product_id_change(self, cr, uid, ids, product):
424 res = self.pool.get('product.product').read(cr, uid, [product], ['uom_id'])[0]
425 uom = res['uom_id'] and res['uom_id'][0]
426 result = {'product_uom':uom}
427 return {'value':result}
429 def action_picking_except(self, cr, uid, ids):
430 self.write(cr, uid, ids, {'state':'picking_except'})
433 def action_compute(self, cr, uid, ids, properties=[]):
435 for production in self.browse(cr, uid, ids):
436 cr.execute('delete from mrp_production_product_line where production_id=%d', (production.id,))
437 cr.execute('delete from mrp_production_workcenter_line where production_id=%d', (production.id,))
438 bom_point = production.bom_id
439 bom_id = production.bom_id.id
441 bom_id = self.pool.get('mrp.bom')._bom_find(cr, uid, production.product_id.id, production.product_uom.id, properties)
443 self.write(cr, uid, [production.id], {'bom_id': bom_id})
444 bom_point = self.pool.get('mrp.bom').browse(cr, uid, [bom_id])[0]
447 raise osv.except_osv('Error', "Couldn't find bill of material for product")
449 #if bom_point.routing_id and bom_point.routing_id.location_id:
450 # self.write(cr, uid, [production.id], {'location_src_id': bom_point.routing_id.location_id.id})
452 factor = production.product_qty * production.product_uom.factor / bom_point.product_uom.factor
453 res = self.pool.get('mrp.bom')._bom_explode(cr, uid, bom_point, factor / bom_point.product_qty, properties)
457 line['production_id'] = production.id
458 self.pool.get('mrp.production.product.line').create(cr, uid, line)
459 for line in results2:
460 line['production_id'] = production.id
461 self.pool.get('mrp.production.workcenter.line').create(cr, uid, line)
464 def action_cancel(self, cr, uid, ids):
465 for production in self.browse(cr, uid, ids):
466 if production.move_created_ids:
467 self.pool.get('stock.move').action_cancel(cr, uid, [x.id for x in production.move_created_ids])
468 self.pool.get('stock.move').action_cancel(cr, uid, [x.id for x in production.move_lines])
469 self.write(cr, uid, ids, {'state':'cancel','move_lines':[(6,0,[])]})
472 #XXX: may be a bug here; lot_lines are unreserved for a few seconds;
473 # between the end of the picking list and the call to this function
474 def action_ready(self, cr, uid, ids):
475 self.write(cr, uid, ids, {'state':'ready'})
476 for production in self.browse(cr, uid, ids):
477 if production.move_prod_id:
478 self.pool.get('stock.move').write(cr, uid, [production.move_prod_id.id],
479 {'location_id':production.location_dest_id.id})
482 #TODO Review materials in function in_prod and prod_end.
483 def action_production_end(self, cr, uid, ids):
485 for production in self.browse(cr, uid, ids):
486 for res in production.move_lines:
487 for move in production.move_created_ids:
488 #XXX must use the orm
489 cr.execute('INSERT INTO stock_move_history_ids \
490 (parent_id, child_id) VALUES (%d,%d)',
492 move_ids.append(res.id)
493 if production.move_created_ids:
494 #TODO There we should handle the residus move creation
495 vals= {'state':'confirmed'}
496 new_moves = [x.id for x in production.move_created_ids]
497 self.pool.get('stock.move').write(cr, uid, new_moves, vals)
499 #XXX Why is it there ? Aren't we suppose to already have a created_move ?
500 source = production.product_id.product_tmpl_id.property_stock_production.id
502 'name':'PROD:'+production.name,
503 'date_planned': production.date_planned,
504 'product_id': production.product_id.id,
505 'product_qty': production.product_qty,
506 'product_uom': production.product_uom.id,
507 'product_uos_qty': production.product_uos and production.product_uos_qty or False,
508 'product_uos': production.product_uos and production.product_uos.id or False,
509 'location_id': source,
510 'location_dest_id': production.location_dest_id.id,
511 'move_dest_id': production.move_prod_id.id,
514 new_moves = [self.pool.get('stock.move').create(cr, uid, vals)]
515 self.write(cr, uid, [production.id],
516 {'move_created_ids': [(6, 'WTF', new_moves)]})
517 if not production.date_finnished:
518 self.write(cr, uid, [production.id],
519 {'date_finnished': time.strftime('%Y-%m-%d %H:%M:%S')})
520 self.pool.get('stock.move').check_assign(cr, uid, new_moves)
521 self.pool.get('stock.move').action_done(cr, uid, new_moves)
522 self._costs_generate(cr, uid, production)
523 self.pool.get('stock.move').action_done(cr, uid, move_ids)
524 self.write(cr, uid, ids, {'state': 'done'})
527 def _costs_generate(self, cr, uid, production):
529 for wc_line in production.workcenter_lines:
530 wc = wc_line.workcenter_id
531 if wc.costs_journal_id and wc.costs_general_account_id:
532 value = wc_line.hour * wc.costs_hour
533 account = wc.costs_hour_account_id.id
534 if value and account:
536 self.pool.get('account.analytic.line').create(cr, uid, {
537 'name': wc_line.name+' (H)',
539 'account_id': account,
540 'general_account_id': wc.costs_general_account_id.id,
541 'journal_id': wc.costs_journal_id.id,
544 if wc.costs_journal_id and wc.costs_general_account_id:
545 value = wc_line.cycle * wc.costs_cycle
546 account = wc.costs_cycle_account_id.id
547 if value and account:
549 self.pool.get('account.analytic.line').create(cr, uid, {
550 'name': wc_line.name+' (C)',
552 'account_id': account,
553 'general_account_id': wc.costs_general_account_id.id,
554 'journal_id': wc.costs_journal_id.id,
559 def action_in_production(self, cr, uid, ids):
561 for production in self.browse(cr, uid, ids):
562 for res in production.move_lines:
563 move_ids.append(res.id)
564 if not production.date_start:
565 self.write(cr, uid, [production.id],
566 {'date_start': time.strftime('%Y-%m-%d %H:%M:%S')})
567 self.pool.get('stock.move').action_done(cr, uid, move_ids)
568 self.write(cr, uid, ids, {'state': 'in_production'})
571 def test_if_product(self, cr, uid, ids):
573 for production in self.browse(cr, uid, ids):
574 if not production.product_lines:
575 if not self.action_compute(cr, uid, [production.id]):
579 def _get_auto_picking(self, cr, uid, production):
582 def action_confirm(self, cr, uid, ids):
584 for production in self.browse(cr, uid, ids):
585 if not production.product_lines:
586 self.action_compute(cr, uid, [production.id])
587 production = self.browse(cr, uid, [production.id])[0]
589 pick_type = 'internal'
591 if production.bom_id.routing_id and production.bom_id.routing_id.location_id:
592 routing_loc = production.bom_id.routing_id.location_id
593 if routing_loc.usage<>'internal':
595 address_id = routing_loc.address_id and routing_loc.address_id.id or False
596 routing_loc = routing_loc.id
597 picking_id = self.pool.get('stock.picking').create(cr, uid, {
598 'origin': (production.origin or '').split(':')[0] +':'+production.name,
602 'address_id': address_id,
603 'auto_picking': self._get_auto_picking(cr, uid, production),
607 source = production.product_id.product_tmpl_id.property_stock_production.id
609 'name':'PROD:'+production.name,
610 'date_planned': production.date_planned,
611 'product_id': production.product_id.id,
612 'product_qty': production.product_qty,
613 'product_uom': production.product_uom.id,
614 'product_uos_qty': production.product_uos and production.product_uos_qty or False,
615 'product_uos': production.product_uos and production.product_uos.id or False,
616 'location_id': source,
617 'location_dest_id': production.location_dest_id.id,
618 'move_dest_id': production.move_prod_id.id,
621 res_final_id = self.pool.get('stock.move').create(cr, uid, data)
623 self.write(cr, uid, [production.id], {'move_created_ids': [(6, 'WTF', [res_final_id])]})
625 for line in production.product_lines:
627 newdate = production.date_planned
628 if line.product_id.type in ('product', 'consu'):
629 res_dest_id = self.pool.get('stock.move').create(cr, uid, {
630 'name':'PROD:'+production.name,
631 'date_planned': production.date_planned,
632 'product_id': line.product_id.id,
633 'product_qty': line.product_qty,
634 'product_uom': line.product_uom.id,
635 'product_uos_qty': line.product_uos and line.product_uos_qty or False,
636 'product_uos': line.product_uos and line.product_uos.id or False,
637 'location_id': routing_loc or production.location_src_id.id,
638 'location_dest_id': source,
639 'move_dest_id': res_final_id,
642 moves.append(res_dest_id)
643 move_id = self.pool.get('stock.move').create(cr, uid, {
644 'name':'PROD:'+production.name,
645 'picking_id':picking_id,
646 'product_id': line.product_id.id,
647 'product_qty': line.product_qty,
648 'product_uom': line.product_uom.id,
649 'product_uos_qty': line.product_uos and line.product_uos_qty or False,
650 'product_uos': line.product_uos and line.product_uos.id or False,
651 'date_planned': newdate,
652 'move_dest_id': res_dest_id,
653 'location_id': production.location_src_id.id,
654 'location_dest_id': routing_loc or production.location_src_id.id,
657 proc_id = self.pool.get('mrp.procurement').create(cr, uid, {
658 'name': (production.origin or '').split(':')[0] + ':' + production.name,
659 'origin': (production.origin or '').split(':')[0] + ':' + production.name,
660 'date_planned': newdate,
661 'product_id': line.product_id.id,
662 'product_qty': line.product_qty,
663 'product_uom': line.product_uom.id,
664 'product_uos_qty': line.product_uos and line.product_qty or False,
665 'product_uos': line.product_uos and line.product_uos.id or False,
666 'location_id': production.location_src_id.id,
667 'procure_method': line.product_id.procure_method,
670 wf_service = netsvc.LocalService("workflow")
671 wf_service.trg_validate(uid, 'mrp.procurement', proc_id, 'button_confirm', cr)
673 wf_service = netsvc.LocalService("workflow")
674 wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
675 self.write(cr, uid, [production.id], {'picking_id':picking_id, 'move_lines': [(6,0,moves)], 'state':'confirmed'})
678 def force_production(self, cr, uid, ids, *args):
679 pick_obj = self.pool.get('stock.picking')
680 pick_obj.force_assign(cr, uid, [prod.picking_id.id for prod in self.browse(cr, uid, ids)])
686 class stock_move(osv.osv):
688 _inherit = 'stock.move'
690 'production_id': fields.many2one('mrp.production', 'Production', select=True),
694 class mrp_production_workcenter_line(osv.osv):
695 _name = 'mrp.production.workcenter.line'
696 _description = 'Production workcenters used'
698 'name': fields.char('Name', size=64, required=True),
699 'workcenter_id': fields.many2one('mrp.workcenter', 'Workcenter', required=True),
700 'cycle': fields.float('Nbr of cycle'),
701 'hour': fields.float('Nbr of hour'),
702 'sequence': fields.integer('Sequence', required=True),
703 'production_id': fields.many2one('mrp.production', 'Production Order', select=True),
706 'sequence': lambda *a: 1,
707 'hour': lambda *a: 0,
708 'cycle': lambda *a: 0,
710 mrp_production_workcenter_line()
712 class mrp_production_product_line(osv.osv):
713 _name = 'mrp.production.product.line'
714 _description = 'Production scheduled products'
716 'name': fields.char('Name', size=64, required=True),
717 'product_id': fields.many2one('product.product', 'Product', required=True),
718 'product_qty': fields.float('Product Qty', required=True),
719 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True),
720 'product_uos_qty': fields.float('Product UOS Qty'),
721 'product_uos': fields.many2one('product.uom', 'Product UOS'),
722 'production_id': fields.many2one('mrp.production', 'Production Order', select=True),
724 mrp_production_product_line()
726 # ------------------------------------------------------------------
728 # ------------------------------------------------------------------
730 # Produce, Buy or Find products and place a move
731 # then wizard for picking lists & move
733 class mrp_procurement(osv.osv):
734 _name = "mrp.procurement"
735 _description = "Procurement"
737 'name': fields.char('Name', size=64, required=True),
738 'origin': fields.char('Origin', size=64,
739 help="Reference of the document that created this procurement.\n"
740 "This is automatically completed by Open ERP."),
741 'priority': fields.selection([('0','Not urgent'),('1','Normal'),('2','Urgent'),('3','Very Urgent')], 'Priority', required=True),
742 'date_planned': fields.datetime('Scheduled date', required=True),
743 'date_close': fields.datetime('Date Closed'),
744 'product_id': fields.many2one('product.product', 'Product', required=True),
745 'product_qty': fields.float('Quantity', required=True),
746 'product_uom': fields.many2one('product.uom', 'Product UoM', required=True),
747 'product_uos_qty': fields.float('UoS Quantity'),
748 'product_uos': fields.many2one('product.uom', 'Product UoS'),
749 'move_id': fields.many2one('stock.move', 'Reservation', ondelete='set null'),
751 'bom_id': fields.many2one('mrp.bom', 'BoM', ondelete='cascade', select=True),
753 'close_move': fields.boolean('Close Move at end', required=True),
754 'location_id': fields.many2one('stock.location', 'Location', required=True),
755 'procure_method': fields.selection([('make_to_stock','from stock'),('make_to_order','on order')], 'Procurement Method', states={'draft':[('readonly',False)], 'confirmed':[('readonly',False)]},
756 readonly=True, required=True, help="If you encode manually a procurement, you probably want to use" \
757 " a make to order method."),
759 'purchase_id': fields.many2one('purchase.order', 'Purchase Order'),
760 'purchase_line_id': fields.many2one('purchase.order.line', 'Purchase Order Line'),
762 'property_ids': fields.many2many('mrp.property', 'mrp_procurement_property_rel', 'procurement_id','property_id', 'Properties'),
764 'message': fields.char('Latest error', size=64),
765 'state': fields.selection([('draft','Draft'),('confirmed','Confirmed'),('exception','Exception'),('running','Running'),('cancel','Cancel'),('done','Done'),('waiting','Waiting')], 'Status')
768 'state': lambda *a: 'draft',
769 'priority': lambda *a: '1',
770 'date_planned': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
771 'close_move': lambda *a: 0,
772 'procure_method': lambda *a: 'make_to_order',
775 def unlink(self, cr, uid, ids):
776 procurements = self.read(cr, uid, ids, ['state'])
778 for s in procurements:
779 if s['state'] in ['draft','cancel']:
780 unlink_ids.append(s['id'])
782 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Procurement Order(s) which are in %s State!' % s['state']))
783 osv.osv.unlink(self, cr, uid, unlink_ids)
786 def onchange_product_id(self, cr, uid, ids, product_id, context={}):
788 w=self.pool.get('product.product').browse(cr,uid,product_id, context)
790 'product_uom':w.uom_id.id,
791 'product_uos':w.uos_id and w.uos_id.id or w.uom_id.id
796 def check_product(self, cr, uid, ids):
797 for procurement in self.browse(cr, uid, ids):
798 if procurement.product_id.type in ('product', 'consu'):
802 def check_move_cancel(self, cr, uid, ids, context={}):
804 for procurement in self.browse(cr, uid, ids, context):
805 if procurement.move_id:
806 if not procurement.move_id.state=='cancel':
810 def check_move_done(self, cr, uid, ids, context={}):
812 for proc in self.browse(cr, uid, ids, context):
814 if not proc.move_id.state=='done':
819 # This method may be overrided by objects that override mrp.procurment
820 # for computing their own purpose
822 def _quantity_compute_get(self, cr, uid, proc, context={}):
823 if proc.product_id.type=='product':
824 return proc.move_id.product_uos_qty
827 def _uom_compute_get(self, cr, uid, proc, context={}):
828 if proc.product_id.type=='product':
829 if proc.move_id.product_uos:
830 return proc.move_id.product_uos.id
834 # Return the quantity of product shipped/produced/served, wich may be
835 # different from the planned quantity
837 def quantity_get(self, cr, uid, id, context={}):
838 proc = self.browse(cr, uid, id, context)
839 result = self._quantity_compute_get(cr, uid, proc, context)
841 result = proc.product_qty
844 def uom_get(self, cr, uid, id, context=None):
845 proc = self.browse(cr, uid, id, context)
846 result = self._uom_compute_get(cr, uid, proc, context)
848 result = proc.product_uom.id
851 def check_waiting(self, cr, uid, ids, context=[]):
852 for procurement in self.browse(cr, uid, ids, context=context):
853 if procurement.move_id and procurement.move_id.state=='auto':
857 def check_produce_service(self, cr, uid, procurement, context=[]):
860 def check_produce_product(self, cr, uid, procurement, context=[]):
861 properties = [x.id for x in procurement.property_ids]
862 bom_id = self.pool.get('mrp.bom')._bom_find(cr, uid, procurement.product_id.id, procurement.product_uom.id, properties)
864 cr.execute('update mrp_procurement set message=%s where id=%d', ('No BoM defined for this product !', procurement.id))
868 def check_make_to_stock(self, cr, uid, ids, context={}):
870 for procurement in self.browse(cr, uid, ids, context=context):
871 if procurement.product_id.type=='service':
872 ok = ok and self._check_make_to_stock_service(cr, uid, procurement, context)
874 ok = ok and self._check_make_to_stock_product(cr, uid, procurement, context)
877 def check_produce(self, cr, uid, ids, context={}):
879 user = self.pool.get('res.users').browse(cr, uid, uid)
880 for procurement in self.browse(cr, uid, ids):
881 if procurement.product_id.product_tmpl_id.supply_method=='buy':
882 if procurement.product_id.seller_ids:
883 partner = procurement.product_id.seller_ids[0].name
884 if user.company_id and user.company_id.partner_id:
885 if partner.id == user.company_id.partner_id.id:
888 if procurement.product_id.product_tmpl_id.type=='service':
889 res = res and self.check_produce_service(cr, uid, procurement, context)
891 res = res and self.check_produce_product(cr, uid, procurement, context)
896 def check_buy(self, cr, uid, ids):
897 user = self.pool.get('res.users').browse(cr, uid, uid)
898 for procurement in self.browse(cr, uid, ids):
899 if procurement.product_id.product_tmpl_id.supply_method=='produce':
901 if not procurement.product_id.seller_ids:
902 cr.execute('update mrp_procurement set message=%s where id=%d', ('No supplier defined for this product !', procurement.id))
904 partner = procurement.product_id.seller_ids[0].name
905 if user.company_id and user.company_id.partner_id:
906 if partner.id == user.company_id.partner_id.id:
908 address_id = self.pool.get('res.partner').address_get(cr, uid, [partner.id], ['delivery'])['delivery']
910 cr.execute('update mrp_procurement set message=%s where id=%d', ('No address defined for the supplier', procurement.id))
914 def test_cancel(self, cr, uid, ids):
915 for record in self.browse(cr, uid, ids):
916 if record.move_id and record.move_id.state=='cancel':
920 def action_confirm(self, cr, uid, ids, context={}):
921 for procurement in self.browse(cr, uid, ids):
922 if procurement.product_id.type in ('product', 'consu'):
923 if not procurement.move_id:
924 source = procurement.location_id.id
925 if procurement.procure_method=='make_to_order':
926 source = procurement.product_id.product_tmpl_id.property_stock_procurement.id
927 id = self.pool.get('stock.move').create(cr, uid, {
928 'name': 'PROC:'+procurement.name,
929 'location_id': source,
930 'location_dest_id': procurement.location_id.id,
931 'product_id': procurement.product_id.id,
932 'product_qty':procurement.product_qty,
933 'product_uom': procurement.product_uom.id,
934 'date_planned': procurement.date_planned,
937 self.write(cr, uid, [procurement.id], {'move_id': id, 'close_move':1})
940 if procurement.procure_method=='make_to_stock' and procurement.move_id.state in ('waiting',):
941 id = self.pool.get('stock.move').write(cr, uid, [procurement.move_id.id], {'state':'confirmed'})
942 self.write(cr, uid, ids, {'state':'confirmed','message':''})
945 def action_move_assigned(self, cr, uid, ids):
946 self.write(cr, uid, ids, {'state':'running','message':'from stock: products assigned.'})
949 def _check_make_to_stock_service(self, cr, uid, procurement, context={}):
952 def _check_make_to_stock_product(self, cr, uid, procurement, context={}):
954 if procurement.move_id:
955 id = procurement.move_id.id
956 if not (procurement.move_id.state in ('done','assigned','cancel')):
957 ok = ok and self.pool.get('stock.move').action_assign(cr, uid, [id])
958 cr.execute('select count(id) from stock_warehouse_orderpoint where product_id=%d', (procurement.product_id.id,))
959 if not cr.fetchone()[0]:
960 cr.execute('update mrp_procurement set message=%s where id=%d', ('from stock and no minimum orderpoint rule defined', procurement.id))
963 def action_produce_assign_service(self, cr, uid, ids, context={}):
964 for procurement in self.browse(cr, uid, ids):
965 self.write(cr, uid, [procurement.id], {'state':'running'})
968 def action_produce_assign_product(self, cr, uid, ids, context={}):
970 company = self.pool.get('res.users').browse(cr, uid, uid, context).company_id
971 for procurement in self.browse(cr, uid, ids):
972 res_id = procurement.move_id.id
973 loc_id = procurement.location_id.id
974 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)
975 newdate = newdate - DateTime.RelativeDateTime(days=company.manufacturing_lead)
976 produce_id = self.pool.get('mrp.production').create(cr, uid, {
977 'origin': procurement.origin,
978 'product_id': procurement.product_id.id,
979 'product_qty': procurement.product_qty,
980 'product_uom': procurement.product_uom.id,
981 'product_uos_qty': procurement.product_uos and procurement.product_uos_qty or False,
982 'product_uos': procurement.product_uos and procurement.product_uos.id or False,
983 'location_src_id': procurement.location_id.id,
984 'location_dest_id': procurement.location_id.id,
985 'bom_id': procurement.bom_id and procurement.bom_id.id or False,
986 'date_planned': newdate,
987 'move_prod_id': res_id,
989 self.write(cr, uid, [procurement.id], {'state':'running'})
990 bom_result = self.pool.get('mrp.production').action_compute(cr, uid,
991 [produce_id], properties=[x.id for x in procurement.property_ids])
992 wf_service = netsvc.LocalService("workflow")
993 wf_service.trg_validate(uid, 'mrp.production', produce_id, 'button_confirm', cr)
996 def action_po_assign(self, cr, uid, ids, context={}):
998 company = self.pool.get('res.users').browse(cr, uid, uid, context).company_id
999 for procurement in self.browse(cr, uid, ids):
1000 res_id = procurement.move_id.id
1001 partner = procurement.product_id.seller_ids[0].name
1002 partner_id = partner.id
1003 address_id = self.pool.get('res.partner').address_get(cr, uid, [partner_id], ['delivery'])['delivery']
1004 pricelist_id = partner.property_product_pricelist_purchase.id
1006 uom_id = procurement.product_id.uom_po_id.id
1008 qty = self.pool.get('product.uom')._compute_qty(cr, uid, procurement.product_uom.id, procurement.product_qty, uom_id)
1009 if procurement.product_id.seller_ids[0].qty:
1010 qty=max(qty,procurement.product_id.seller_ids[0].qty)
1012 price = self.pool.get('product.pricelist').price_get(cr, uid, [pricelist_id], procurement.product_id.id, qty, False, {'uom': uom_id})[pricelist_id]
1014 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)
1015 newdate = newdate - DateTime.RelativeDateTime(days=company.po_lead)
1016 context.update({'lang':partner.lang})
1017 product=self.pool.get('product.product').browse(cr,uid,procurement.product_id.id,context=context)
1020 'name': product.name,
1022 'product_id': procurement.product_id.id,
1023 'product_uom': uom_id,
1024 'price_unit': price,
1025 'date_planned': newdate.strftime('%Y-%m-%d %H:%M:%S'),
1026 'move_dest_id': res_id,
1027 'notes':product.description_purchase,
1030 taxes_ids = procurement.product_id.product_tmpl_id.supplier_taxes_id
1031 self.pool.get('account.fiscal.position').map_tax(cr, uid, partner, taxes_ids)
1033 'taxes_id':[(6,0,taxes_ids)]
1035 purchase_id = self.pool.get('purchase.order').create(cr, uid, {
1036 'origin': procurement.origin,
1037 'partner_id': partner_id,
1038 'partner_address_id': address_id,
1039 'location_id': procurement.location_id.id,
1040 'pricelist_id': pricelist_id,
1041 'order_line': [(0,0,line)]
1043 self.write(cr, uid, [procurement.id], {'state':'running', 'purchase_id':purchase_id})
1046 def action_cancel(self, cr, uid, ids):
1048 for proc in self.browse(cr, uid, ids):
1050 todo.append(proc.move_id.id)
1052 self.pool.get('stock.move').action_cancel(cr, uid, [proc.move_id.id])
1053 self.write(cr, uid, ids, {'state':'cancel'})
1055 wf_service = netsvc.LocalService("workflow")
1057 wf_service.trg_trigger(uid, 'mrp.procurement', id, cr)
1061 def action_check_finnished(self, cr, uid, ids):
1064 def action_check(self, cr, uid, ids):
1066 for procurement in self.browse(cr, uid, ids):
1067 if procurement.move_id.state=='assigned' or procurement.move_id.state=='done':
1068 self.action_done(cr, uid, [procurement.id])
1072 def action_done(self, cr, uid, ids):
1073 for procurement in self.browse(cr, uid, ids):
1074 if procurement.move_id:
1075 if procurement.close_move and (procurement.move_id.state <> 'done'):
1076 self.pool.get('stock.move').action_done(cr, uid, [procurement.move_id.id])
1077 res = self.write(cr, uid, ids, {'state':'done', 'date_close':time.strftime('%Y-%m-%d')})
1079 wf_service = netsvc.LocalService("workflow")
1081 wf_service.trg_trigger(uid, 'mrp.procurement', id, cr)
1083 def run_scheduler(self, cr, uid, automatic=False, use_new_cursor=False, context=None):
1085 use_new_cursor: False or the dbname
1089 self._procure_confirm(cr, uid, use_new_cursor=use_new_cursor, context=context)
1090 self._procure_orderpoint_confirm(cr, uid, automatic=automatic,\
1091 use_new_cursor=use_new_cursor, context=context)
1095 class stock_warehouse_orderpoint(osv.osv):
1096 _name = "stock.warehouse.orderpoint"
1097 _description = "Orderpoint minimum rule"
1099 'name': fields.char('Name', size=32, required=True),
1100 'active': fields.boolean('Active'),
1101 'logic': fields.selection([('max','Order to Max'),('price','Best price (not yet active!)')], 'Reordering Mode', required=True),
1102 'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse', required=True),
1103 'location_id': fields.many2one('stock.location', 'Location', required=True),
1104 'product_id': fields.many2one('product.product', 'Product', required=True, domain=[('type','=','product')]),
1105 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True ),
1106 'product_min_qty': fields.float('Min Quantity', required=True,
1107 help="When the virtual stock goes belong the Min Quantity, Open ERP generates "\
1108 "a procurement to bring the virtual stock to the Max Quantity."),
1109 'product_max_qty': fields.float('Max Quantity', required=True,
1110 help="When the virtual stock goes belong the Min Quantity, Open ERP generates "\
1111 "a procurement to bring the virtual stock to the Max Quantity."),
1112 'qty_multiple': fields.integer('Qty Multiple', required=True,
1113 help="The procurement quantity will by rounded up to this multiple."),
1114 'procurement_id': fields.many2one('mrp.procurement', 'Purchase Order')
1117 'active': lambda *a: 1,
1118 'logic': lambda *a: 'max',
1119 'qty_multiple': lambda *a: 1,
1120 'name': lambda x,y,z,c: x.pool.get('ir.sequence').get(y,z,'mrp.warehouse.orderpoint') or '',
1121 'product_uom': lambda sel, cr, uid, context: context.get('product_uom', False),
1123 def onchange_warehouse_id(self, cr, uid, ids, warehouse_id, context={}):
1125 w=self.pool.get('stock.warehouse').browse(cr,uid,warehouse_id, context)
1126 v = {'location_id':w.lot_stock_id.id}
1129 def onchange_product_id(self, cr, uid, ids, product_id, context={}):
1131 prod=self.pool.get('product.product').browse(cr,uid,product_id)
1132 v = {'product_uom':prod.uom_id.id}
1135 stock_warehouse_orderpoint()
1138 class StockMove(osv.osv):
1139 _inherit = 'stock.move'
1141 'procurements': fields.one2many('mrp.procurement', 'move_id', 'Procurements'),
1143 def copy(self, cr, uid, id, default=None, context=None):
1144 default = default or {}
1145 default['procurements'] = []
1146 return super(StockMove, self).copy(cr, uid, id, default, context)
1148 def _action_explode(self, cr, uid, move, context={}):
1149 if move.product_id.supply_method=='produce' and move.product_id.procure_method=='make_to_order':
1150 bis = self.pool.get('mrp.bom').search(cr, uid, [
1151 ('product_id','=',move.product_id.id),
1152 ('bom_id','=',False),
1153 ('type','=','phantom')])
1155 factor = move.product_qty
1156 bom_point = self.pool.get('mrp.bom').browse(cr, uid, bis[0])
1157 res = self.pool.get('mrp.bom')._bom_explode(cr, uid, bom_point, factor, [])
1158 dest = move.product_id.product_tmpl_id.property_stock_production.id
1160 if move.state=='assigned':
1164 'picking_id': move.picking_id.id,
1165 'product_id': line['product_id'],
1166 'product_uom': line['product_uom'],
1167 'product_qty': line['product_qty'],
1168 'product_uos': line['product_uos'],
1169 'product_uos_qty': line['product_uos_qty'],
1170 'move_dest_id': move.id,
1172 'location_dest_id': dest,
1173 'move_history_ids': [(6,0,[move.id])],
1174 'move_history_ids2': [(6,0,[])],
1177 mid = self.pool.get('stock.move').copy(cr, uid, move.id, default=valdef)
1178 prodobj = self.pool.get('product.product').browse(cr, uid, line['product_id'], context=context)
1179 proc_id = self.pool.get('mrp.procurement').create(cr, uid, {
1180 'name': (move.picking_id.origin or ''),
1181 'origin': (move.picking_id.origin or ''),
1182 'date_planned': move.date_planned,
1183 'product_id': line['product_id'],
1184 'product_qty': line['product_qty'],
1185 'product_uom': line['product_uom'],
1186 'product_uos_qty': line['product_uos'] and line['product_uos_qty'] or False,
1187 'product_uos': line['product_uos'],
1188 'location_id': move.location_id.id,
1189 'procure_method': prodobj.procure_method,
1192 wf_service = netsvc.LocalService("workflow")
1193 wf_service.trg_validate(uid, 'mrp.procurement', proc_id, 'button_confirm', cr)
1194 self.pool.get('stock.move').write(cr, uid, [move.id], {
1195 'location_id': move.location_dest_id.id,
1196 'auto_validate': True,
1197 'picking_id': False,
1198 'location_id': dest,
1201 for m in self.pool.get('mrp.procurement').search(cr, uid, [('move_id','=',move.id)], context):
1202 wf_service = netsvc.LocalService("workflow")
1203 wf_service.trg_validate(uid, 'mrp.procurement', m, 'button_wait_done', cr)
1208 class StockPicking(osv.osv):
1209 _inherit = 'stock.picking'
1211 def test_finnished(self, cursor, user, ids):
1212 wf_service = netsvc.LocalService("workflow")
1213 res = super(StockPicking, self).test_finnished(cursor, user, ids)
1214 for picking in self.browse(cursor, user, ids):
1215 for move in picking.move_lines:
1216 if move.state == 'done' and move.procurements:
1217 for procurement in move.procurements:
1218 wf_service.trg_validate(user, 'mrp.procurement',
1219 procurement.id, 'button_check', cursor)
1223 # Explode picking by replacing phantom BoMs
1225 def action_explode(self, cr, uid, picks, *args):
1226 for move in self.pool.get('stock.move').browse(cr, uid, picks):
1227 self.pool.get('stock.move')._action_explode(cr, uid, move)
1232 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: