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';
413 def unlink(self, cr, uid, ids):
414 productions = self.read(cr, uid, ids, ['state'])
416 for s in productions:
417 if s['state'] in ['draft','cancel']:
418 unlink_ids.append(s['id'])
420 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Production Order(s) which are in %s State!' % s['state']))
421 return osv.osv.unlink(self, cr, uid, unlink_ids)
423 def location_id_change(self, cr, uid, ids, src, dest, context={}):
427 return {'value': {'location_dest_id': src}}
430 def product_id_change(self, cr, uid, ids, product):
433 res = self.pool.get('product.product').read(cr, uid, [product], ['uom_id'])[0]
434 uom = res['uom_id'] and res['uom_id'][0]
435 result = {'product_uom':uom}
436 return {'value':result}
438 def action_picking_except(self, cr, uid, ids):
439 self.write(cr, uid, ids, {'state':'picking_except'})
442 def action_compute(self, cr, uid, ids, properties=[]):
444 for production in self.browse(cr, uid, ids):
445 cr.execute('delete from mrp_production_product_line where production_id=%d', (production.id,))
446 cr.execute('delete from mrp_production_workcenter_line where production_id=%d', (production.id,))
447 bom_point = production.bom_id
448 bom_id = production.bom_id.id
450 bom_id = self.pool.get('mrp.bom')._bom_find(cr, uid, production.product_id.id, production.product_uom.id, properties)
452 self.write(cr, uid, [production.id], {'bom_id': bom_id})
453 bom_point = self.pool.get('mrp.bom').browse(cr, uid, [bom_id])[0]
456 raise osv.except_osv('Error', "Couldn't find bill of material for product")
458 #if bom_point.routing_id and bom_point.routing_id.location_id:
459 # self.write(cr, uid, [production.id], {'location_src_id': bom_point.routing_id.location_id.id})
461 factor = production.product_qty * production.product_uom.factor / bom_point.product_uom.factor
462 res = self.pool.get('mrp.bom')._bom_explode(cr, uid, bom_point, factor / bom_point.product_qty, properties)
466 line['production_id'] = production.id
467 self.pool.get('mrp.production.product.line').create(cr, uid, line)
468 for line in results2:
469 line['production_id'] = production.id
470 self.pool.get('mrp.production.workcenter.line').create(cr, uid, line)
473 def action_cancel(self, cr, uid, ids):
474 for production in self.browse(cr, uid, ids):
475 if production.move_created_ids:
476 self.pool.get('stock.move').action_cancel(cr, uid, [x.id for x in production.move_created_ids])
477 self.pool.get('stock.move').action_cancel(cr, uid, [x.id for x in production.move_lines])
478 self.write(cr, uid, ids, {'state':'cancel','move_lines':[(6,0,[])]})
481 #XXX: may be a bug here; lot_lines are unreserved for a few seconds;
482 # between the end of the picking list and the call to this function
483 def action_ready(self, cr, uid, ids):
484 self.write(cr, uid, ids, {'state':'ready'})
485 for production in self.browse(cr, uid, ids):
486 if production.move_prod_id:
487 self.pool.get('stock.move').write(cr, uid, [production.move_prod_id.id],
488 {'location_id':production.location_dest_id.id})
491 #TODO Review materials in function in_prod and prod_end.
492 def action_production_end(self, cr, uid, ids):
494 for production in self.browse(cr, uid, ids):
495 for res in production.move_lines:
496 for move in production.move_created_ids:
497 #XXX must use the orm
498 cr.execute('INSERT INTO stock_move_history_ids \
499 (parent_id, child_id) VALUES (%d,%d)',
501 move_ids.append(res.id)
502 if production.move_created_ids:
503 #TODO There we should handle the residus move creation
504 vals= {'state':'confirmed'}
505 new_moves = [x.id for x in production.move_created_ids]
506 self.pool.get('stock.move').write(cr, uid, new_moves, vals)
508 #XXX Why is it there ? Aren't we suppose to already have a created_move ?
509 source = production.product_id.product_tmpl_id.property_stock_production.id
511 'name':'PROD:'+production.name,
512 'date_planned': production.date_planned,
513 'product_id': production.product_id.id,
514 'product_qty': production.product_qty,
515 'product_uom': production.product_uom.id,
516 'product_uos_qty': production.product_uos and production.product_uos_qty or False,
517 'product_uos': production.product_uos and production.product_uos.id or False,
518 'location_id': source,
519 'location_dest_id': production.location_dest_id.id,
520 'move_dest_id': production.move_prod_id.id,
523 new_moves = [self.pool.get('stock.move').create(cr, uid, vals)]
524 self.write(cr, uid, [production.id],
525 {'move_created_ids': [(6, 'WTF', new_moves)]})
526 if not production.date_finnished:
527 self.write(cr, uid, [production.id],
528 {'date_finnished': time.strftime('%Y-%m-%d %H:%M:%S')})
529 self.pool.get('stock.move').check_assign(cr, uid, new_moves)
530 self.pool.get('stock.move').action_done(cr, uid, new_moves)
531 self._costs_generate(cr, uid, production)
532 self.pool.get('stock.move').action_done(cr, uid, move_ids)
533 self.write(cr, uid, ids, {'state': 'done'})
536 def _costs_generate(self, cr, uid, production):
538 for wc_line in production.workcenter_lines:
539 wc = wc_line.workcenter_id
540 if wc.costs_journal_id and wc.costs_general_account_id:
541 value = wc_line.hour * wc.costs_hour
542 account = wc.costs_hour_account_id.id
543 if value and account:
545 self.pool.get('account.analytic.line').create(cr, uid, {
546 'name': wc_line.name+' (H)',
548 'account_id': account,
549 'general_account_id': wc.costs_general_account_id.id,
550 'journal_id': wc.costs_journal_id.id,
553 if wc.costs_journal_id and wc.costs_general_account_id:
554 value = wc_line.cycle * wc.costs_cycle
555 account = wc.costs_cycle_account_id.id
556 if value and account:
558 self.pool.get('account.analytic.line').create(cr, uid, {
559 'name': wc_line.name+' (C)',
561 'account_id': account,
562 'general_account_id': wc.costs_general_account_id.id,
563 'journal_id': wc.costs_journal_id.id,
568 def action_in_production(self, cr, uid, ids):
570 for production in self.browse(cr, uid, ids):
571 for res in production.move_lines:
572 move_ids.append(res.id)
573 if not production.date_start:
574 self.write(cr, uid, [production.id],
575 {'date_start': time.strftime('%Y-%m-%d %H:%M:%S')})
576 self.pool.get('stock.move').action_done(cr, uid, move_ids)
577 self.write(cr, uid, ids, {'state': 'in_production'})
580 def test_if_product(self, cr, uid, ids):
582 for production in self.browse(cr, uid, ids):
583 if not production.product_lines:
584 if not self.action_compute(cr, uid, [production.id]):
588 def _get_auto_picking(self, cr, uid, production):
591 def action_confirm(self, cr, uid, ids):
593 for production in self.browse(cr, uid, ids):
594 if not production.product_lines:
595 self.action_compute(cr, uid, [production.id])
596 production = self.browse(cr, uid, [production.id])[0]
598 pick_type = 'internal'
600 if production.bom_id.routing_id and production.bom_id.routing_id.location_id:
601 routing_loc = production.bom_id.routing_id.location_id
602 if routing_loc.usage<>'internal':
604 address_id = routing_loc.address_id and routing_loc.address_id.id or False
605 routing_loc = routing_loc.id
606 picking_id = self.pool.get('stock.picking').create(cr, uid, {
607 'origin': (production.origin or '').split(':')[0] +':'+production.name,
611 'address_id': address_id,
612 'auto_picking': self._get_auto_picking(cr, uid, production),
616 source = production.product_id.product_tmpl_id.property_stock_production.id
618 'name':'PROD:'+production.name,
619 'date_planned': production.date_planned,
620 'product_id': production.product_id.id,
621 'product_qty': production.product_qty,
622 'product_uom': production.product_uom.id,
623 'product_uos_qty': production.product_uos and production.product_uos_qty or False,
624 'product_uos': production.product_uos and production.product_uos.id or False,
625 'location_id': source,
626 'location_dest_id': production.location_dest_id.id,
627 'move_dest_id': production.move_prod_id.id,
630 res_final_id = self.pool.get('stock.move').create(cr, uid, data)
632 self.write(cr, uid, [production.id], {'move_created_ids': [(6, 'WTF', [res_final_id])]})
634 for line in production.product_lines:
636 newdate = production.date_planned
637 if line.product_id.type in ('product', 'consu'):
638 res_dest_id = self.pool.get('stock.move').create(cr, uid, {
639 'name':'PROD:'+production.name,
640 'date_planned': production.date_planned,
641 'product_id': line.product_id.id,
642 'product_qty': line.product_qty,
643 'product_uom': line.product_uom.id,
644 'product_uos_qty': line.product_uos and line.product_uos_qty or False,
645 'product_uos': line.product_uos and line.product_uos.id or False,
646 'location_id': routing_loc or production.location_src_id.id,
647 'location_dest_id': source,
648 'move_dest_id': res_final_id,
651 moves.append(res_dest_id)
652 move_id = self.pool.get('stock.move').create(cr, uid, {
653 'name':'PROD:'+production.name,
654 'picking_id':picking_id,
655 'product_id': line.product_id.id,
656 'product_qty': line.product_qty,
657 'product_uom': line.product_uom.id,
658 'product_uos_qty': line.product_uos and line.product_uos_qty or False,
659 'product_uos': line.product_uos and line.product_uos.id or False,
660 'date_planned': newdate,
661 'move_dest_id': res_dest_id,
662 'location_id': production.location_src_id.id,
663 'location_dest_id': routing_loc or production.location_src_id.id,
666 proc_id = self.pool.get('mrp.procurement').create(cr, uid, {
667 'name': (production.origin or '').split(':')[0] + ':' + production.name,
668 'origin': (production.origin or '').split(':')[0] + ':' + production.name,
669 'date_planned': newdate,
670 'product_id': line.product_id.id,
671 'product_qty': line.product_qty,
672 'product_uom': line.product_uom.id,
673 'product_uos_qty': line.product_uos and line.product_qty or False,
674 'product_uos': line.product_uos and line.product_uos.id or False,
675 'location_id': production.location_src_id.id,
676 'procure_method': line.product_id.procure_method,
679 wf_service = netsvc.LocalService("workflow")
680 wf_service.trg_validate(uid, 'mrp.procurement', proc_id, 'button_confirm', cr)
682 wf_service = netsvc.LocalService("workflow")
683 wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
684 self.write(cr, uid, [production.id], {'picking_id':picking_id, 'move_lines': [(6,0,moves)], 'state':'confirmed'})
687 def force_production(self, cr, uid, ids, *args):
688 pick_obj = self.pool.get('stock.picking')
689 pick_obj.force_assign(cr, uid, [prod.picking_id.id for prod in self.browse(cr, uid, ids)])
695 class stock_move(osv.osv):
697 _inherit = 'stock.move'
699 'production_id': fields.many2one('mrp.production', 'Production', select=True),
703 class mrp_production_workcenter_line(osv.osv):
704 _name = 'mrp.production.workcenter.line'
705 _description = 'Production workcenters used'
707 'name': fields.char('Name', size=64, required=True),
708 'workcenter_id': fields.many2one('mrp.workcenter', 'Workcenter', required=True),
709 'cycle': fields.float('Nbr of cycle'),
710 'hour': fields.float('Nbr of hour'),
711 'sequence': fields.integer('Sequence', required=True),
712 'production_id': fields.many2one('mrp.production', 'Production Order', select=True),
715 'sequence': lambda *a: 1,
716 'hour': lambda *a: 0,
717 'cycle': lambda *a: 0,
719 mrp_production_workcenter_line()
721 class mrp_production_product_line(osv.osv):
722 _name = 'mrp.production.product.line'
723 _description = 'Production scheduled products'
725 'name': fields.char('Name', size=64, required=True),
726 'product_id': fields.many2one('product.product', 'Product', required=True),
727 'product_qty': fields.float('Product Qty', required=True),
728 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True),
729 'product_uos_qty': fields.float('Product UOS Qty'),
730 'product_uos': fields.many2one('product.uom', 'Product UOS'),
731 'production_id': fields.many2one('mrp.production', 'Production Order', select=True),
733 mrp_production_product_line()
735 # ------------------------------------------------------------------
737 # ------------------------------------------------------------------
739 # Produce, Buy or Find products and place a move
740 # then wizard for picking lists & move
742 class mrp_procurement(osv.osv):
743 _name = "mrp.procurement"
744 _description = "Procurement"
746 'name': fields.char('Name', size=64, required=True),
747 'origin': fields.char('Origin', size=64,
748 help="Reference of the document that created this procurement.\n"
749 "This is automatically completed by Open ERP."),
750 'priority': fields.selection([('0','Not urgent'),('1','Normal'),('2','Urgent'),('3','Very Urgent')], 'Priority', required=True),
751 'date_planned': fields.datetime('Scheduled date', required=True),
752 'date_close': fields.datetime('Date Closed'),
753 'product_id': fields.many2one('product.product', 'Product', required=True),
754 'product_qty': fields.float('Quantity', required=True),
755 'product_uom': fields.many2one('product.uom', 'Product UoM', required=True),
756 'product_uos_qty': fields.float('UoS Quantity'),
757 'product_uos': fields.many2one('product.uom', 'Product UoS'),
758 'move_id': fields.many2one('stock.move', 'Reservation', ondelete='set null'),
760 'bom_id': fields.many2one('mrp.bom', 'BoM', ondelete='cascade', select=True),
762 'close_move': fields.boolean('Close Move at end', required=True),
763 'location_id': fields.many2one('stock.location', 'Location', required=True),
764 'procure_method': fields.selection([('make_to_stock','from stock'),('make_to_order','on order')], 'Procurement Method', states={'draft':[('readonly',False)], 'confirmed':[('readonly',False)]},
765 readonly=True, required=True, help="If you encode manually a procurement, you probably want to use" \
766 " a make to order method."),
768 'purchase_id': fields.many2one('purchase.order', 'Purchase Order'),
769 'purchase_line_id': fields.many2one('purchase.order.line', 'Purchase Order Line'),
771 'property_ids': fields.many2many('mrp.property', 'mrp_procurement_property_rel', 'procurement_id','property_id', 'Properties'),
773 'message': fields.char('Latest error', size=64),
774 'state': fields.selection([('draft','Draft'),('confirmed','Confirmed'),('exception','Exception'),('running','Running'),('cancel','Cancel'),('done','Done'),('waiting','Waiting')], 'Status')
777 'state': lambda *a: 'draft',
778 'priority': lambda *a: '1',
779 'date_planned': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
780 'close_move': lambda *a: 0,
781 'procure_method': lambda *a: 'make_to_order',
784 def unlink(self, cr, uid, ids):
785 procurements = self.read(cr, uid, ids, ['state'])
787 for s in procurements:
788 if s['state'] in ['draft','cancel']:
789 unlink_ids.append(s['id'])
791 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Procurement Order(s) which are in %s State!' % s['state']))
792 return osv.osv.unlink(self, cr, uid, unlink_ids)
794 def onchange_product_id(self, cr, uid, ids, product_id, context={}):
796 w=self.pool.get('product.product').browse(cr,uid,product_id, context)
798 'product_uom':w.uom_id.id,
799 'product_uos':w.uos_id and w.uos_id.id or w.uom_id.id
804 def check_product(self, cr, uid, ids):
805 for procurement in self.browse(cr, uid, ids):
806 if procurement.product_id.type in ('product', 'consu'):
810 def check_move_cancel(self, cr, uid, ids, context={}):
812 for procurement in self.browse(cr, uid, ids, context):
813 if procurement.move_id:
814 if not procurement.move_id.state=='cancel':
818 def check_move_done(self, cr, uid, ids, context={}):
820 for proc in self.browse(cr, uid, ids, context):
822 if not proc.move_id.state=='done':
827 # This method may be overrided by objects that override mrp.procurment
828 # for computing their own purpose
830 def _quantity_compute_get(self, cr, uid, proc, context={}):
831 if proc.product_id.type=='product':
832 return proc.move_id.product_uos_qty
835 def _uom_compute_get(self, cr, uid, proc, context={}):
836 if proc.product_id.type=='product':
837 if proc.move_id.product_uos:
838 return proc.move_id.product_uos.id
842 # Return the quantity of product shipped/produced/served, wich may be
843 # different from the planned quantity
845 def quantity_get(self, cr, uid, id, context={}):
846 proc = self.browse(cr, uid, id, context)
847 result = self._quantity_compute_get(cr, uid, proc, context)
849 result = proc.product_qty
852 def uom_get(self, cr, uid, id, context=None):
853 proc = self.browse(cr, uid, id, context)
854 result = self._uom_compute_get(cr, uid, proc, context)
856 result = proc.product_uom.id
859 def check_waiting(self, cr, uid, ids, context=[]):
860 for procurement in self.browse(cr, uid, ids, context=context):
861 if procurement.move_id and procurement.move_id.state=='auto':
865 def check_produce_service(self, cr, uid, procurement, context=[]):
868 def check_produce_product(self, cr, uid, procurement, context=[]):
869 properties = [x.id for x in procurement.property_ids]
870 bom_id = self.pool.get('mrp.bom')._bom_find(cr, uid, procurement.product_id.id, procurement.product_uom.id, properties)
872 cr.execute('update mrp_procurement set message=%s where id=%d', ('No BoM defined for this product !', procurement.id))
876 def check_make_to_stock(self, cr, uid, ids, context={}):
878 for procurement in self.browse(cr, uid, ids, context=context):
879 if procurement.product_id.type=='service':
880 ok = ok and self._check_make_to_stock_service(cr, uid, procurement, context)
882 ok = ok and self._check_make_to_stock_product(cr, uid, procurement, context)
885 def check_produce(self, cr, uid, ids, context={}):
887 user = self.pool.get('res.users').browse(cr, uid, uid)
888 for procurement in self.browse(cr, uid, ids):
889 if procurement.product_id.product_tmpl_id.supply_method=='buy':
890 if procurement.product_id.seller_ids:
891 partner = procurement.product_id.seller_ids[0].name
892 if user.company_id and user.company_id.partner_id:
893 if partner.id == user.company_id.partner_id.id:
896 if procurement.product_id.product_tmpl_id.type=='service':
897 res = res and self.check_produce_service(cr, uid, procurement, context)
899 res = res and self.check_produce_product(cr, uid, procurement, context)
904 def check_buy(self, cr, uid, ids):
905 user = self.pool.get('res.users').browse(cr, uid, uid)
906 for procurement in self.browse(cr, uid, ids):
907 if procurement.product_id.product_tmpl_id.supply_method=='produce':
909 if not procurement.product_id.seller_ids:
910 cr.execute('update mrp_procurement set message=%s where id=%d', ('No supplier defined for this product !', procurement.id))
912 partner = procurement.product_id.seller_ids[0].name
913 if user.company_id and user.company_id.partner_id:
914 if partner.id == user.company_id.partner_id.id:
916 address_id = self.pool.get('res.partner').address_get(cr, uid, [partner.id], ['delivery'])['delivery']
918 cr.execute('update mrp_procurement set message=%s where id=%d', ('No address defined for the supplier', procurement.id))
922 def test_cancel(self, cr, uid, ids):
923 for record in self.browse(cr, uid, ids):
924 if record.move_id and record.move_id.state=='cancel':
928 def action_confirm(self, cr, uid, ids, context={}):
929 for procurement in self.browse(cr, uid, ids):
930 if procurement.product_id.type in ('product', 'consu'):
931 if not procurement.move_id:
932 source = procurement.location_id.id
933 if procurement.procure_method=='make_to_order':
934 source = procurement.product_id.product_tmpl_id.property_stock_procurement.id
935 id = self.pool.get('stock.move').create(cr, uid, {
936 'name': 'PROC:'+procurement.name,
937 'location_id': source,
938 'location_dest_id': procurement.location_id.id,
939 'product_id': procurement.product_id.id,
940 'product_qty':procurement.product_qty,
941 'product_uom': procurement.product_uom.id,
942 'date_planned': procurement.date_planned,
945 self.write(cr, uid, [procurement.id], {'move_id': id, 'close_move':1})
948 if procurement.procure_method=='make_to_stock' and procurement.move_id.state in ('waiting',):
949 id = self.pool.get('stock.move').write(cr, uid, [procurement.move_id.id], {'state':'confirmed'})
950 self.write(cr, uid, ids, {'state':'confirmed','message':''})
953 def action_move_assigned(self, cr, uid, ids):
954 self.write(cr, uid, ids, {'state':'running','message':'from stock: products assigned.'})
957 def _check_make_to_stock_service(self, cr, uid, procurement, context={}):
960 def _check_make_to_stock_product(self, cr, uid, procurement, context={}):
962 if procurement.move_id:
963 id = procurement.move_id.id
964 if not (procurement.move_id.state in ('done','assigned','cancel')):
965 ok = ok and self.pool.get('stock.move').action_assign(cr, uid, [id])
966 cr.execute('select count(id) from stock_warehouse_orderpoint where product_id=%d', (procurement.product_id.id,))
967 if not cr.fetchone()[0]:
968 cr.execute('update mrp_procurement set message=%s where id=%d', ('from stock and no minimum orderpoint rule defined', procurement.id))
971 def action_produce_assign_service(self, cr, uid, ids, context={}):
972 for procurement in self.browse(cr, uid, ids):
973 self.write(cr, uid, [procurement.id], {'state':'running'})
976 def action_produce_assign_product(self, cr, uid, ids, context={}):
978 company = self.pool.get('res.users').browse(cr, uid, uid, context).company_id
979 for procurement in self.browse(cr, uid, ids):
980 res_id = procurement.move_id.id
981 loc_id = procurement.location_id.id
982 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)
983 newdate = newdate - DateTime.RelativeDateTime(days=company.manufacturing_lead)
984 produce_id = self.pool.get('mrp.production').create(cr, uid, {
985 'origin': procurement.origin,
986 'product_id': procurement.product_id.id,
987 'product_qty': procurement.product_qty,
988 'product_uom': procurement.product_uom.id,
989 'product_uos_qty': procurement.product_uos and procurement.product_uos_qty or False,
990 'product_uos': procurement.product_uos and procurement.product_uos.id or False,
991 'location_src_id': procurement.location_id.id,
992 'location_dest_id': procurement.location_id.id,
993 'bom_id': procurement.bom_id and procurement.bom_id.id or False,
994 'date_planned': newdate,
995 'move_prod_id': res_id,
997 self.write(cr, uid, [procurement.id], {'state':'running'})
998 bom_result = self.pool.get('mrp.production').action_compute(cr, uid,
999 [produce_id], properties=[x.id for x in procurement.property_ids])
1000 wf_service = netsvc.LocalService("workflow")
1001 wf_service.trg_validate(uid, 'mrp.production', produce_id, 'button_confirm', cr)
1004 def action_po_assign(self, cr, uid, ids, context={}):
1006 company = self.pool.get('res.users').browse(cr, uid, uid, context).company_id
1007 for procurement in self.browse(cr, uid, ids):
1008 res_id = procurement.move_id.id
1009 partner = procurement.product_id.seller_ids[0].name
1010 partner_id = partner.id
1011 address_id = self.pool.get('res.partner').address_get(cr, uid, [partner_id], ['delivery'])['delivery']
1012 pricelist_id = partner.property_product_pricelist_purchase.id
1014 uom_id = procurement.product_id.uom_po_id.id
1016 qty = self.pool.get('product.uom')._compute_qty(cr, uid, procurement.product_uom.id, procurement.product_qty, uom_id)
1017 if procurement.product_id.seller_ids[0].qty:
1018 qty=max(qty,procurement.product_id.seller_ids[0].qty)
1020 price = self.pool.get('product.pricelist').price_get(cr, uid, [pricelist_id], procurement.product_id.id, qty, False, {'uom': uom_id})[pricelist_id]
1022 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)
1023 newdate = newdate - DateTime.RelativeDateTime(days=company.po_lead)
1024 context.update({'lang':partner.lang})
1025 product=self.pool.get('product.product').browse(cr,uid,procurement.product_id.id,context=context)
1028 'name': product.name,
1030 'product_id': procurement.product_id.id,
1031 'product_uom': uom_id,
1032 'price_unit': price,
1033 'date_planned': newdate.strftime('%Y-%m-%d %H:%M:%S'),
1034 'move_dest_id': res_id,
1035 'notes':product.description_purchase,
1038 taxes_ids = procurement.product_id.product_tmpl_id.supplier_taxes_id
1039 self.pool.get('account.fiscal.position').map_tax(cr, uid, partner, taxes_ids)
1041 'taxes_id':[(6,0,taxes_ids)]
1043 purchase_id = self.pool.get('purchase.order').create(cr, uid, {
1044 'origin': procurement.origin,
1045 'partner_id': partner_id,
1046 'partner_address_id': address_id,
1047 'location_id': procurement.location_id.id,
1048 'pricelist_id': pricelist_id,
1049 'order_line': [(0,0,line)]
1051 self.write(cr, uid, [procurement.id], {'state':'running', 'purchase_id':purchase_id})
1054 def action_cancel(self, cr, uid, ids):
1056 for proc in self.browse(cr, uid, ids):
1057 if proc.move_id and proc.move_id.state=='waiting':
1058 todo.append(proc.move_id.id)
1060 self.pool.get('stock.move').write(cr, uid, todo, {'state':'assigned'})
1061 self.write(cr, uid, ids, {'state':'cancel'})
1063 wf_service = netsvc.LocalService("workflow")
1065 wf_service.trg_trigger(uid, 'mrp.procurement', id, cr)
1069 def action_check_finnished(self, cr, uid, ids):
1072 def action_check(self, cr, uid, ids):
1074 for procurement in self.browse(cr, uid, ids):
1075 if procurement.move_id.state=='assigned' or procurement.move_id.state=='done':
1076 self.action_done(cr, uid, [procurement.id])
1080 def action_done(self, cr, uid, ids):
1081 for procurement in self.browse(cr, uid, ids):
1082 if procurement.move_id:
1083 if procurement.close_move and (procurement.move_id.state <> 'done'):
1084 self.pool.get('stock.move').action_done(cr, uid, [procurement.move_id.id])
1085 res = self.write(cr, uid, ids, {'state':'done', 'date_close':time.strftime('%Y-%m-%d')})
1087 wf_service = netsvc.LocalService("workflow")
1089 wf_service.trg_trigger(uid, 'mrp.procurement', id, cr)
1091 def run_scheduler(self, cr, uid, automatic=False, use_new_cursor=False, context=None):
1093 use_new_cursor: False or the dbname
1097 self._procure_confirm(cr, uid, use_new_cursor=use_new_cursor, context=context)
1098 self._procure_orderpoint_confirm(cr, uid, automatic=automatic,\
1099 use_new_cursor=use_new_cursor, context=context)
1103 class stock_warehouse_orderpoint(osv.osv):
1104 _name = "stock.warehouse.orderpoint"
1105 _description = "Orderpoint minimum rule"
1107 'name': fields.char('Name', size=32, required=True),
1108 'active': fields.boolean('Active'),
1109 'logic': fields.selection([('max','Order to Max'),('price','Best price (not yet active!)')], 'Reordering Mode', required=True),
1110 'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse', required=True),
1111 'location_id': fields.many2one('stock.location', 'Location', required=True),
1112 'product_id': fields.many2one('product.product', 'Product', required=True, domain=[('type','=','product')]),
1113 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True ),
1114 'product_min_qty': fields.float('Min Quantity', required=True,
1115 help="When the virtual stock goes belong the Min Quantity, Open ERP generates "\
1116 "a procurement to bring the virtual stock to the Max Quantity."),
1117 'product_max_qty': fields.float('Max Quantity', required=True,
1118 help="When the virtual stock goes belong the Min Quantity, Open ERP generates "\
1119 "a procurement to bring the virtual stock to the Max Quantity."),
1120 'qty_multiple': fields.integer('Qty Multiple', required=True,
1121 help="The procurement quantity will by rounded up to this multiple."),
1122 'procurement_id': fields.many2one('mrp.procurement', 'Purchase Order')
1125 'active': lambda *a: 1,
1126 'logic': lambda *a: 'max',
1127 'qty_multiple': lambda *a: 1,
1128 'name': lambda x,y,z,c: x.pool.get('ir.sequence').get(y,z,'mrp.warehouse.orderpoint') or '',
1129 'product_uom': lambda sel, cr, uid, context: context.get('product_uom', False),
1131 def onchange_warehouse_id(self, cr, uid, ids, warehouse_id, context={}):
1133 w=self.pool.get('stock.warehouse').browse(cr,uid,warehouse_id, context)
1134 v = {'location_id':w.lot_stock_id.id}
1137 def onchange_product_id(self, cr, uid, ids, product_id, context={}):
1139 prod=self.pool.get('product.product').browse(cr,uid,product_id)
1140 v = {'product_uom':prod.uom_id.id}
1143 stock_warehouse_orderpoint()
1146 class StockMove(osv.osv):
1147 _inherit = 'stock.move'
1149 'procurements': fields.one2many('mrp.procurement', 'move_id', 'Procurements'),
1151 def copy(self, cr, uid, id, default=None, context=None):
1152 default = default or {}
1153 default['procurements'] = []
1154 return super(StockMove, self).copy(cr, uid, id, default, context)
1156 def _action_explode(self, cr, uid, move, context={}):
1157 if move.product_id.supply_method=='produce' and move.product_id.procure_method=='make_to_order':
1158 bis = self.pool.get('mrp.bom').search(cr, uid, [
1159 ('product_id','=',move.product_id.id),
1160 ('bom_id','=',False),
1161 ('type','=','phantom')])
1163 factor = move.product_qty
1164 bom_point = self.pool.get('mrp.bom').browse(cr, uid, bis[0])
1165 res = self.pool.get('mrp.bom')._bom_explode(cr, uid, bom_point, factor, [])
1166 dest = move.product_id.product_tmpl_id.property_stock_production.id
1168 if move.state=='assigned':
1172 'picking_id': move.picking_id.id,
1173 'product_id': line['product_id'],
1174 'product_uom': line['product_uom'],
1175 'product_qty': line['product_qty'],
1176 'product_uos': line['product_uos'],
1177 'product_uos_qty': line['product_uos_qty'],
1178 'move_dest_id': move.id,
1180 'location_dest_id': dest,
1181 'move_history_ids': [(6,0,[move.id])],
1182 'move_history_ids2': [(6,0,[])],
1185 mid = self.pool.get('stock.move').copy(cr, uid, move.id, default=valdef)
1186 prodobj = self.pool.get('product.product').browse(cr, uid, line['product_id'], context=context)
1187 proc_id = self.pool.get('mrp.procurement').create(cr, uid, {
1188 'name': (move.picking_id.origin or ''),
1189 'origin': (move.picking_id.origin or ''),
1190 'date_planned': move.date_planned,
1191 'product_id': line['product_id'],
1192 'product_qty': line['product_qty'],
1193 'product_uom': line['product_uom'],
1194 'product_uos_qty': line['product_uos'] and line['product_uos_qty'] or False,
1195 'product_uos': line['product_uos'],
1196 'location_id': move.location_id.id,
1197 'procure_method': prodobj.procure_method,
1200 wf_service = netsvc.LocalService("workflow")
1201 wf_service.trg_validate(uid, 'mrp.procurement', proc_id, 'button_confirm', cr)
1202 self.pool.get('stock.move').write(cr, uid, [move.id], {
1203 'location_id': move.location_dest_id.id,
1204 'auto_validate': True,
1205 'picking_id': False,
1206 'location_id': dest,
1209 for m in self.pool.get('mrp.procurement').search(cr, uid, [('move_id','=',move.id)], context):
1210 wf_service = netsvc.LocalService("workflow")
1211 wf_service.trg_validate(uid, 'mrp.procurement', m, 'button_wait_done', cr)
1216 class StockPicking(osv.osv):
1217 _inherit = 'stock.picking'
1219 def test_finnished(self, cursor, user, ids):
1220 wf_service = netsvc.LocalService("workflow")
1221 res = super(StockPicking, self).test_finnished(cursor, user, ids)
1222 for picking in self.browse(cursor, user, ids):
1223 for move in picking.move_lines:
1224 if move.state == 'done' and move.procurements:
1225 for procurement in move.procurements:
1226 wf_service.trg_validate(user, 'mrp.procurement',
1227 procurement.id, 'button_check', cursor)
1231 # Explode picking by replacing phantom BoMs
1233 def action_explode(self, cr, uid, picks, *args):
1234 for move in self.pool.get('stock.move').browse(cr, uid, picks):
1235 self.pool.get('stock.move')._action_explode(cr, uid, move)
1240 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: