1 # -*- encoding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 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 finished products are needed." \
112 "Set 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)
149 ok = ((name=='child_complete_ids') and (bom.product_id.supply_method=='produce'))
150 if bom.type=='phantom' or ok:
151 sids = self.pool.get('mrp.bom').search(cr, uid, [('bom_id','=',False),('product_id','=',bom.product_id.id)])
153 bom2 = self.pool.get('mrp.bom').browse(cr, uid, sids[0], context=context)
154 result[bom.id] += map(lambda x: x.id, bom2.bom_lines)
156 def _compute_type(self, cr, uid, ids, field_name, arg, context):
157 res = dict(map(lambda x: (x,''), ids))
158 for line in self.browse(cr, uid, ids):
159 if line.type=='phantom' and not line.bom_id:
162 if line.bom_lines or line.type=='phantom':
164 if line.product_id.supply_method=='produce':
165 if line.product_id.procure_method=='make_to_stock':
166 res[line.id] = 'stock'
168 res[line.id] = 'order'
171 'name': fields.char('Name', size=64, required=True),
172 'code': fields.char('Code', size=16),
173 'active': fields.boolean('Active'),
174 'type': fields.selection([('normal','Normal BoM'),('phantom','Sets / Phantom')], 'BoM Type', required=True, help=
175 "Use a phantom bill of material in raw materials lines that have to be " \
176 "automatically computed in on eproduction order and not one per level." \
177 "If you put \"Phantom/Set\" at the root level of a bill of material " \
178 "it is considered as a set or pack: the products are replaced by the components " \
179 "between the sale order to the picking without going through the production order." \
180 "The normal BoM will generate one production order per BoM level."),
181 'method': fields.function(_compute_type, string='Method', method=True, type='selection', selection=[('',''),('stock','On Stock'),('order','On Order'),('set','Set / Pack')]),
182 'date_start': fields.date('Valid From', help="Validity of this BoM or component. Keep empty if it's always valid."),
183 'date_stop': fields.date('Valid Until', help="Validity of this BoM or component. Keep empty if it's always valid."),
184 'sequence': fields.integer('Sequence'),
185 'position': fields.char('Internal Ref.', size=64, help="Reference to a position in an external plan."),
186 'product_id': fields.many2one('product.product', 'Product', required=True),
187 'product_uos_qty': fields.float('Product UOS Qty'),
188 'product_uos': fields.many2one('product.uom', 'Product UOS'),
189 'product_qty': fields.float('Product Qty', required=True),
190 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True),
191 'product_rounding': fields.float('Product Rounding', help="Rounding applied on the product quantity. For integer only values, put 1.0"),
192 '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."),
193 'bom_lines': fields.one2many('mrp.bom', 'bom_id', 'BoM Lines'),
194 'bom_id': fields.many2one('mrp.bom', 'Parent BoM', ondelete='cascade', select=True),
195 'routing_id': fields.many2one('mrp.routing', 'Routing', help="The list of operations (list of workcenters) to produce the finished product. The routing is mainly used to compute workcenter costs during operations and to plan futur loads on workcenters based on production plannification."),
196 'property_ids': fields.many2many('mrp.property', 'mrp_bom_property_rel', 'bom_id','property_id', 'Properties'),
197 'revision_ids': fields.one2many('mrp.bom.revision', 'bom_id', 'BoM Revisions'),
198 'revision_type': fields.selection([('numeric','numeric indices'),('alpha','alphabetical indices')], 'indice type'),
199 'child_ids': fields.function(_child_compute,relation='mrp.bom', method=True, string="BoM Hyerarchy", type='many2many'),
200 'child_complete_ids': fields.function(_child_compute,relation='mrp.bom', method=True, string="BoM Hyerarchy", type='many2many')
203 'active': lambda *a: 1,
204 'product_efficiency': lambda *a: 1.0,
205 'product_qty': lambda *a: 1.0,
206 'product_rounding': lambda *a: 1.0,
207 'type': lambda *a: 'normal',
211 ('bom_qty_zero', 'CHECK (product_qty>0)', 'All product quantities must be greater than 0.\n' \
212 'You should install the mrp_subproduct module if you want to manage extra products on BoMs !'),
215 def _check_recursion(self, cr, uid, ids):
218 cr.execute('select distinct bom_id from mrp_bom where id in ('+','.join(map(str,ids))+')')
219 ids = filter(None, map(lambda x:x[0], cr.fetchall()))
225 (_check_recursion, 'Error ! You can not create recursive BoM.', ['parent_id'])
229 def onchange_product_id(self, cr, uid, ids, product_id, name, context={}):
231 prod=self.pool.get('product.product').browse(cr,uid,[product_id])[0]
232 v = {'product_uom':prod.uom_id.id}
234 v['name'] = prod.name
238 def _bom_find(self, cr, uid, product_id, product_uom, properties=[]):
240 # Why searching on BoM without parent ?
241 cr.execute('select id from mrp_bom where product_id=%s and bom_id is null order by sequence', (product_id,))
242 ids = map(lambda x: x[0], cr.fetchall())
245 for bom in self.pool.get('mrp.bom').browse(cr, uid, ids):
247 for prop_id in bom.property_ids:
248 if prop_id.id in properties:
250 if (prop>max_prop) or ((max_prop==0) and not result):
255 def _bom_explode(self, cr, uid, bom, factor, properties, addthis=False, level=0):
256 factor = factor / (bom.product_efficiency or 1.0)
257 factor = rounding(factor, bom.product_rounding)
258 if factor<bom.product_rounding:
259 factor = bom.product_rounding
263 if bom.type=='phantom' and not bom.bom_lines:
264 newbom = self._bom_find(cr, uid, bom.product_id.id, bom.product_uom.id, properties)
266 res = self._bom_explode(cr, uid, self.browse(cr, uid, [newbom])[0], factor*bom.product_qty, properties, addthis=True, level=level+10)
267 result = result + res[0]
268 result2 = result2 + res[1]
273 if addthis and not bom.bom_lines:
276 'name': bom.product_id.name,
277 'product_id': bom.product_id.id,
278 'product_qty': bom.product_qty * factor,
279 'product_uom': bom.product_uom.id,
280 'product_uos_qty': bom.product_uos and bom.product_uos_qty * factor or False,
281 'product_uos': bom.product_uos and bom.product_uos.id or False,
284 for wc_use in bom.routing_id.workcenter_lines:
285 wc = wc_use.workcenter_id
286 d, m = divmod(factor, wc_use.workcenter_id.capacity_per_cycle)
287 mult = (d + (m and 1.0 or 0.0))
288 cycle = mult * wc_use.cycle_nbr
290 'name': bom.routing_id.name,
291 'workcenter_id': wc.id,
292 'sequence': level+(wc_use.sequence or 0),
294 'hour': float(wc_use.hour_nbr*mult + (wc.time_start+wc.time_stop+cycle*wc.time_cycle) * (wc.time_efficiency or 1.0)),
296 for bom2 in bom.bom_lines:
297 res = self._bom_explode(cr, uid, bom2, factor, properties, addthis=True, level=level+10)
298 result = result + res[0]
299 result2 = result2 + res[1]
300 return result, result2
302 def set_indices(self, cr, uid, ids, context = {}):
303 if not ids or (ids and not ids[0]):
305 res = self.read(cr, uid, ids, ['revision_ids', 'revision_type'])
306 rev_ids = res[0]['revision_ids']
309 for rev_id in rev_ids:
310 if res[0]['revision_type'] == 'numeric':
311 self.pool.get('mrp.bom.revision').write(cr, uid, [rev_id], {'indice' : idx})
313 self.pool.get('mrp.bom.revision').write(cr, uid, [rev_id], {'indice' : "%c"%(idx+96,)})
319 class mrp_bom_revision(osv.osv):
320 _name = 'mrp.bom.revision'
321 _description = 'Bill of material revisions'
323 'name': fields.char('Modification name', size=64, required=True),
324 'description': fields.text('Description'),
325 'date': fields.date('Modification Date'),
326 'indice': fields.char('Revision', size=16),
327 'last_indice': fields.char('last indice', size=64),
328 'author_id': fields.many2one('res.users', 'Author'),
329 'bom_id': fields.many2one('mrp.bom', 'BoM', select=True),
333 'author_id': lambda x,y,z,c: z,
334 'date': lambda *a: time.strftime('%Y-%m-%d'),
342 return round(f / r) * r
344 class mrp_production(osv.osv):
345 _name = 'mrp.production'
346 _description = 'Production'
347 _date_name = 'date_planned'
349 def _get_sale_order(self,cr,uid,ids,field_name=False):
350 move_obj=self.pool.get('stock.move')
351 def get_parent_move(move_id):
352 move = move_obj.browse(cr,uid,move_id)
353 if move.move_dest_id:
354 return get_parent_move(move.move_dest_id.id)
356 productions=self.read(cr,uid,ids,['id','move_prod_id'])
358 for production in productions:
359 res[production['id']]=False
360 if production.get('move_prod_id',False):
361 parent_move_line=get_parent_move(production['move_prod_id'][0])
363 move = move_obj.browse(cr,uid,parent_move_line)
364 if field_name=='name':
365 res[production['id']]=move.sale_line_id and move.sale_line_id.order_id.name or False
366 if field_name=='client_order_ref':
367 res[production['id']]=move.sale_line_id and move.sale_line_id.order_id.client_order_ref or False
370 def _production_calc(self, cr, uid, ids, prop, unknow_none, context={}):
372 for prod in self.browse(cr, uid, ids, context=context):
377 for wc in prod.workcenter_lines:
378 result[prod.id]['hour_total'] += wc.hour
379 result[prod.id]['cycle_total'] += wc.cycle
382 def _production_date(self, cr, uid, ids, prop, unknow_none, context={}):
384 for prod in self.browse(cr, uid, ids, context=context):
385 result[prod.id] = prod.date_planned[:10]
388 def _sale_name_calc(self, cr, uid, ids, prop, unknow_none, unknow_dict):
389 return self._get_sale_order(cr,uid,ids,field_name='name')
391 def _sale_ref_calc(self, cr, uid, ids, prop, unknow_none, unknow_dict):
392 return self._get_sale_order(cr,uid,ids,field_name='client_order_ref')
395 'name': fields.char('Reference', size=64, required=True),
396 'origin': fields.char('Origin', size=64),
397 'priority': fields.selection([('0','Not urgent'),('1','Normal'),('2','Urgent'),('3','Very Urgent')], 'Priority'),
399 'product_id': fields.many2one('product.product', 'Product', required=True, domain=[('type','<>','service')]),
400 'product_qty': fields.float('Product Qty', required=True),
401 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True),
402 'product_uos_qty': fields.float('Product UoS Qty'),
403 'product_uos': fields.many2one('product.uom', 'Product UoS'),
405 'location_src_id': fields.many2one('stock.location', 'Raw Materials Location', required=True,
406 help="Location where the system will look for products used in raw materials."),
407 'location_dest_id': fields.many2one('stock.location', 'Finished Products Location', required=True,
408 help="Location where the system will stock the finished products."),
410 'date_planned_date': fields.function(_production_date, method=True, type='date', string='Planned Date'),
411 'date_planned': fields.datetime('Scheduled date', required=True, select=1),
412 'date_start': fields.datetime('Start Date'),
413 'date_finnished': fields.datetime('End Date'),
415 'bom_id': fields.many2one('mrp.bom', 'Bill of Material', domain=[('bom_id','=',False)]),
416 'routing_id': fields.many2one('mrp.routing', string='Routing', on_delete='set null'),
418 'picking_id': fields.many2one('stock.picking', 'Packing list', readonly=True,
419 help="This is the internal picking list take bring the raw materials to the production plan."),
420 'move_prod_id': fields.many2one('stock.move', 'Move product', readonly=True),
421 'move_lines': fields.many2many('stock.move', 'mrp_production_move_ids', 'production_id', 'move_id', 'Products Consummed'),
423 'move_created_ids': fields.one2many('stock.move', 'production_id', 'Moves Created'),
424 'product_lines': fields.one2many('mrp.production.product.line', 'production_id', 'Scheduled goods'),
425 'workcenter_lines': fields.one2many('mrp.production.workcenter.line', 'production_id', 'Workcenters Utilisation'),
427 '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),
428 'hour_total': fields.function(_production_calc, method=True, type='float', string='Total Hours', multi='workorder'),
429 'cycle_total': fields.function(_production_calc, method=True, type='float', string='Total Cycles', multi='workorder'),
431 'sale_name': fields.function(_sale_name_calc, method=True, type='char', string='Sale Name'),
432 'sale_ref': fields.function(_sale_ref_calc, method=True, type='char', string='Sale Ref'),
435 'priority': lambda *a: '1',
436 'state': lambda *a: 'draft',
437 'date_planned': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
438 'product_qty': lambda *a: 1.0,
439 'name': lambda x,y,z,c: x.pool.get('ir.sequence').get(y,z,'mrp.production') or '/',
441 _order = 'date_planned asc, priority desc';
442 def unlink(self, cr, uid, ids, context=None):
443 productions = self.read(cr, uid, ids, ['state'])
445 for s in productions:
446 if s['state'] in ['draft','cancel']:
447 unlink_ids.append(s['id'])
449 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Production Order(s) which are in %s State!' % s['state']))
450 return osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
452 def copy(self, cr, uid, id, default=None,context=None):
456 'name': self.pool.get('ir.sequence').get(cr, uid, 'mrp.production'),
459 return super(mrp_production, self).copy(cr, uid, id, default, context)
461 def location_id_change(self, cr, uid, ids, src, dest, context={}):
465 return {'value': {'location_dest_id': src}}
468 def product_id_change(self, cr, uid, ids, product):
471 res = self.pool.get('product.product').read(cr, uid, [product], ['uom_id'])[0]
472 uom = res['uom_id'] and res['uom_id'][0]
473 result = {'product_uom':uom}
474 return {'value':result}
476 def action_picking_except(self, cr, uid, ids):
477 self.write(cr, uid, ids, {'state':'picking_except'})
480 def action_compute(self, cr, uid, ids, properties=[]):
482 for production in self.browse(cr, uid, ids):
483 cr.execute('delete from mrp_production_product_line where production_id=%s', (production.id,))
484 cr.execute('delete from mrp_production_workcenter_line where production_id=%s', (production.id,))
485 bom_point = production.bom_id
486 bom_id = production.bom_id.id
488 bom_id = self.pool.get('mrp.bom')._bom_find(cr, uid, production.product_id.id, production.product_uom.id, properties)
490 bom_point = self.pool.get('mrp.bom').browse(cr, uid, bom_id)
491 routing_id = bom_point.routing_id.id or False
492 self.write(cr, uid, [production.id], {'bom_id': bom_id, 'routing_id': routing_id})
495 raise osv.except_osv('Error', "Couldn't find bill of material for product")
497 #if bom_point.routing_id and bom_point.routing_id.location_id:
498 # self.write(cr, uid, [production.id], {'location_src_id': bom_point.routing_id.location_id.id})
500 factor = production.product_qty * production.product_uom.factor / bom_point.product_uom.factor
501 res = self.pool.get('mrp.bom')._bom_explode(cr, uid, bom_point, factor / bom_point.product_qty, properties)
505 line['production_id'] = production.id
506 self.pool.get('mrp.production.product.line').create(cr, uid, line)
507 for line in results2:
508 line['production_id'] = production.id
509 self.pool.get('mrp.production.workcenter.line').create(cr, uid, line)
512 def action_cancel(self, cr, uid, ids):
513 for production in self.browse(cr, uid, ids):
514 if production.move_created_ids:
515 self.pool.get('stock.move').action_cancel(cr, uid, [x.id for x in production.move_created_ids])
516 self.pool.get('stock.move').action_cancel(cr, uid, [x.id for x in production.move_lines])
517 self.write(cr, uid, ids, {'state':'cancel'}) #,'move_lines':[(6,0,[])]})
520 #XXX: may be a bug here; lot_lines are unreserved for a few seconds;
521 # between the end of the picking list and the call to this function
522 def action_ready(self, cr, uid, ids):
523 self.write(cr, uid, ids, {'state':'ready'})
524 for production in self.browse(cr, uid, ids):
525 if production.move_prod_id:
526 self.pool.get('stock.move').write(cr, uid, [production.move_prod_id.id],
527 {'location_id':production.location_dest_id.id})
530 #TODO Review materials in function in_prod and prod_end.
531 def action_production_end(self, cr, uid, ids):
533 for production in self.browse(cr, uid, ids):
534 for res in production.move_lines:
535 for move in production.move_created_ids:
536 #XXX must use the orm
537 cr.execute('INSERT INTO stock_move_history_ids \
538 (parent_id, child_id) VALUES (%s,%s)',
540 move_ids.append(res.id)
541 vals= {'state':'confirmed'}
542 new_moves = [x.id for x in production.move_created_ids]
543 self.pool.get('stock.move').write(cr, uid, new_moves, vals)
544 if not production.date_finnished:
545 self.write(cr, uid, [production.id],
546 {'date_finnished': time.strftime('%Y-%m-%d %H:%M:%S')})
547 self.pool.get('stock.move').check_assign(cr, uid, new_moves)
548 self.pool.get('stock.move').action_done(cr, uid, new_moves)
549 self._costs_generate(cr, uid, production)
550 self.pool.get('stock.move').action_done(cr, uid, move_ids)
551 self.write(cr, uid, ids, {'state': 'done'})
554 def _costs_generate(self, cr, uid, production):
556 for wc_line in production.workcenter_lines:
557 wc = wc_line.workcenter_id
558 if wc.costs_journal_id and wc.costs_general_account_id:
559 value = wc_line.hour * wc.costs_hour
560 account = wc.costs_hour_account_id.id
561 if value and account:
563 self.pool.get('account.analytic.line').create(cr, uid, {
564 'name': wc_line.name+' (H)',
566 'account_id': account,
567 'general_account_id': wc.costs_general_account_id.id,
568 'journal_id': wc.costs_journal_id.id,
571 if wc.costs_journal_id and wc.costs_general_account_id:
572 value = wc_line.cycle * wc.costs_cycle
573 account = wc.costs_cycle_account_id.id
574 if value and account:
576 self.pool.get('account.analytic.line').create(cr, uid, {
577 'name': wc_line.name+' (C)',
579 'account_id': account,
580 'general_account_id': wc.costs_general_account_id.id,
581 'journal_id': wc.costs_journal_id.id,
586 def action_in_production(self, cr, uid, ids):
588 for production in self.browse(cr, uid, ids):
589 for res in production.move_lines:
590 move_ids.append(res.id)
591 if not production.date_start:
592 self.write(cr, uid, [production.id],
593 {'date_start': time.strftime('%Y-%m-%d %H:%M:%S')})
594 self.pool.get('stock.move').action_done(cr, uid, move_ids)
595 self.write(cr, uid, ids, {'state': 'in_production'})
598 def test_if_product(self, cr, uid, ids):
600 for production in self.browse(cr, uid, ids):
601 if not production.product_lines:
602 if not self.action_compute(cr, uid, [production.id]):
606 def _get_auto_picking(self, cr, uid, production):
609 def action_confirm(self, cr, uid, ids):
612 for production in self.browse(cr, uid, ids):
613 if not production.product_lines:
614 self.action_compute(cr, uid, [production.id])
615 production = self.browse(cr, uid, [production.id])[0]
617 pick_type = 'internal'
619 if production.bom_id.routing_id and production.bom_id.routing_id.location_id:
620 routing_loc = production.bom_id.routing_id.location_id
621 if routing_loc.usage<>'internal':
623 address_id = routing_loc.address_id and routing_loc.address_id.id or False
624 routing_loc = routing_loc.id
625 picking_id = self.pool.get('stock.picking').create(cr, uid, {
626 'origin': (production.origin or '').split(':')[0] +':'+production.name,
630 'address_id': address_id,
631 'auto_picking': self._get_auto_picking(cr, uid, production),
634 source = production.product_id.product_tmpl_id.property_stock_production.id
636 'name':'PROD:'+production.name,
637 'date_planned': production.date_planned,
638 'product_id': production.product_id.id,
639 'product_qty': production.product_qty,
640 'product_uom': production.product_uom.id,
641 'product_uos_qty': production.product_uos and production.product_uos_qty or False,
642 'product_uos': production.product_uos and production.product_uos.id or False,
643 'location_id': source,
644 'location_dest_id': production.location_dest_id.id,
645 'move_dest_id': production.move_prod_id.id,
648 res_final_id = self.pool.get('stock.move').create(cr, uid, data)
650 self.write(cr, uid, [production.id], {'move_created_ids': [(6, 0, [res_final_id])]})
652 for line in production.product_lines:
654 newdate = production.date_planned
655 if line.product_id.type in ('product', 'consu'):
656 res_dest_id = self.pool.get('stock.move').create(cr, uid, {
657 'name':'PROD:'+production.name,
658 'date_planned': production.date_planned,
659 'product_id': line.product_id.id,
660 'product_qty': line.product_qty,
661 'product_uom': line.product_uom.id,
662 'product_uos_qty': line.product_uos and line.product_uos_qty or False,
663 'product_uos': line.product_uos and line.product_uos.id or False,
664 'location_id': routing_loc or production.location_src_id.id,
665 'location_dest_id': source,
666 'move_dest_id': res_final_id,
669 moves.append(res_dest_id)
670 move_id = self.pool.get('stock.move').create(cr, uid, {
671 'name':'PROD:'+production.name,
672 'picking_id':picking_id,
673 'product_id': line.product_id.id,
674 'product_qty': line.product_qty,
675 'product_uom': line.product_uom.id,
676 'product_uos_qty': line.product_uos and line.product_uos_qty or False,
677 'product_uos': line.product_uos and line.product_uos.id or False,
678 'date_planned': newdate,
679 'move_dest_id': res_dest_id,
680 'location_id': production.location_src_id.id,
681 'location_dest_id': routing_loc or production.location_src_id.id,
684 proc_id = self.pool.get('mrp.procurement').create(cr, uid, {
685 'name': (production.origin or '').split(':')[0] + ':' + production.name,
686 'origin': (production.origin or '').split(':')[0] + ':' + production.name,
687 'date_planned': newdate,
688 'product_id': line.product_id.id,
689 'product_qty': line.product_qty,
690 'product_uom': line.product_uom.id,
691 'product_uos_qty': line.product_uos and line.product_qty or False,
692 'product_uos': line.product_uos and line.product_uos.id or False,
693 'location_id': production.location_src_id.id,
694 'procure_method': line.product_id.procure_method,
697 wf_service = netsvc.LocalService("workflow")
698 wf_service.trg_validate(uid, 'mrp.procurement', proc_id, 'button_confirm', cr)
699 proc_ids.append(proc_id)
700 wf_service = netsvc.LocalService("workflow")
701 wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
702 self.write(cr, uid, [production.id], {'picking_id':picking_id, 'move_lines': [(6,0,moves)], 'state':'confirmed'})
705 def force_production(self, cr, uid, ids, *args):
706 pick_obj = self.pool.get('stock.picking')
707 pick_obj.force_assign(cr, uid, [prod.picking_id.id for prod in self.browse(cr, uid, ids)])
713 class stock_move(osv.osv):
715 _inherit = 'stock.move'
717 'production_id': fields.many2one('mrp.production', 'Production', select=True),
721 class mrp_production_workcenter_line(osv.osv):
722 _name = 'mrp.production.workcenter.line'
723 _description = 'Production workcenters used'
725 'name': fields.char('Name', size=64, required=True),
726 'workcenter_id': fields.many2one('mrp.workcenter', 'Workcenter', required=True),
727 'cycle': fields.float('Nbr of cycle', digits=(16,2)),
728 'hour': fields.float('Nbr of hour', digits=(16,2)),
729 'sequence': fields.integer('Sequence', required=True),
730 'production_id': fields.many2one('mrp.production', 'Production Order', select=True),
733 'sequence': lambda *a: 1,
734 'hour': lambda *a: 0,
735 'cycle': lambda *a: 0,
737 mrp_production_workcenter_line()
739 class mrp_production_product_line(osv.osv):
740 _name = 'mrp.production.product.line'
741 _description = 'Production scheduled products'
743 'name': fields.char('Name', size=64, required=True),
744 'product_id': fields.many2one('product.product', 'Product', required=True),
745 'product_qty': fields.float('Product Qty', required=True),
746 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True),
747 'product_uos_qty': fields.float('Product UOS Qty'),
748 'product_uos': fields.many2one('product.uom', 'Product UOS'),
749 'production_id': fields.many2one('mrp.production', 'Production Order', select=True),
751 mrp_production_product_line()
753 # ------------------------------------------------------------------
755 # ------------------------------------------------------------------
757 # Produce, Buy or Find products and place a move
758 # then wizard for picking lists & move
760 class mrp_procurement(osv.osv):
761 _name = "mrp.procurement"
762 _description = "Procurement"
764 'name': fields.char('Name', size=64, required=True),
765 'origin': fields.char('Origin', size=64,
766 help="Reference of the document that created this procurement.\n"
767 "This is automatically completed by Open ERP."),
768 'priority': fields.selection([('0','Not urgent'),('1','Normal'),('2','Urgent'),('3','Very Urgent')], 'Priority', required=True),
769 'date_planned': fields.datetime('Scheduled date', required=True),
770 'date_close': fields.datetime('Date Closed'),
771 'product_id': fields.many2one('product.product', 'Product', required=True),
772 'product_qty': fields.float('Quantity', required=True),
773 'product_uom': fields.many2one('product.uom', 'Product UoM', required=True),
774 'product_uos_qty': fields.float('UoS Quantity'),
775 'product_uos': fields.many2one('product.uom', 'Product UoS'),
776 'move_id': fields.many2one('stock.move', 'Reservation', ondelete='set null'),
778 'bom_id': fields.many2one('mrp.bom', 'BoM', ondelete='cascade', select=True),
780 'close_move': fields.boolean('Close Move at end', required=True),
781 'location_id': fields.many2one('stock.location', 'Location', required=True),
782 'procure_method': fields.selection([('make_to_stock','from stock'),('make_to_order','on order')], 'Procurement Method', states={'draft':[('readonly',False)], 'confirmed':[('readonly',False)]},
783 readonly=True, required=True, help="If you encode manually a procurement, you probably want to use" \
784 " a make to order method."),
786 'purchase_id': fields.many2one('purchase.order', 'Purchase Order'),
787 'note': fields.text('Note'),
789 'property_ids': fields.many2many('mrp.property', 'mrp_procurement_property_rel', 'procurement_id','property_id', 'Properties'),
791 'message': fields.char('Latest error', size=64),
792 'state': fields.selection([
794 ('confirmed','Confirmed'),
795 ('exception','Exception'),
796 ('running','Running'),
800 ('waiting','Waiting')], 'Status')
803 'state': lambda *a: 'draft',
804 'priority': lambda *a: '1',
805 'date_planned': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
806 'close_move': lambda *a: 0,
807 'procure_method': lambda *a: 'make_to_order',
810 def unlink(self, cr, uid, ids, context=None):
811 procurements = self.read(cr, uid, ids, ['state'])
813 for s in procurements:
814 if s['state'] in ['draft','cancel']:
815 unlink_ids.append(s['id'])
817 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Procurement Order(s) which are in %s State!' % s['state']))
818 return osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
820 def onchange_product_id(self, cr, uid, ids, product_id, context={}):
822 w=self.pool.get('product.product').browse(cr,uid,product_id, context)
824 'product_uom':w.uom_id.id,
825 'product_uos':w.uos_id and w.uos_id.id or w.uom_id.id
830 def check_product(self, cr, uid, ids):
831 for procurement in self.browse(cr, uid, ids):
832 if procurement.product_id.type in ('product', 'consu'):
836 def check_move_cancel(self, cr, uid, ids, context={}):
838 for procurement in self.browse(cr, uid, ids, context):
839 if procurement.move_id:
840 if not procurement.move_id.state=='cancel':
844 def check_move_done(self, cr, uid, ids, context={}):
846 for proc in self.browse(cr, uid, ids, context):
848 if not proc.move_id.state=='done':
853 # This method may be overrided by objects that override mrp.procurment
854 # for computing their own purpose
856 def _quantity_compute_get(self, cr, uid, proc, context={}):
857 if proc.product_id.type=='product':
858 if proc.move_id.product_uos:
859 return proc.move_id.product_uos_qty
862 def _uom_compute_get(self, cr, uid, proc, context={}):
863 if proc.product_id.type=='product':
864 if proc.move_id.product_uos:
865 return proc.move_id.product_uos.id
869 # Return the quantity of product shipped/produced/served, wich may be
870 # different from the planned quantity
872 def quantity_get(self, cr, uid, id, context={}):
873 proc = self.browse(cr, uid, id, context)
874 result = self._quantity_compute_get(cr, uid, proc, context)
876 result = proc.product_qty
879 def uom_get(self, cr, uid, id, context=None):
880 proc = self.browse(cr, uid, id, context)
881 result = self._uom_compute_get(cr, uid, proc, context)
883 result = proc.product_uom.id
886 def check_waiting(self, cr, uid, ids, context=[]):
887 for procurement in self.browse(cr, uid, ids, context=context):
888 if procurement.move_id and procurement.move_id.state=='auto':
892 def check_produce_service(self, cr, uid, procurement, context=[]):
895 def check_produce_product(self, cr, uid, procurement, context=[]):
896 properties = [x.id for x in procurement.property_ids]
897 bom_id = self.pool.get('mrp.bom')._bom_find(cr, uid, procurement.product_id.id, procurement.product_uom.id, properties)
899 cr.execute('update mrp_procurement set message=%s where id=%s', (_('No BoM defined for this product !'), procurement.id))
903 def check_make_to_stock(self, cr, uid, ids, context={}):
905 for procurement in self.browse(cr, uid, ids, context=context):
906 if procurement.product_id.type=='service':
907 ok = ok and self._check_make_to_stock_service(cr, uid, procurement, context)
909 ok = ok and self._check_make_to_stock_product(cr, uid, procurement, context)
912 def check_produce(self, cr, uid, ids, context={}):
914 user = self.pool.get('res.users').browse(cr, uid, uid)
915 for procurement in self.browse(cr, uid, ids):
916 if procurement.product_id.product_tmpl_id.supply_method<>'produce':
917 if procurement.product_id.seller_ids:
918 partner = procurement.product_id.seller_ids[0].name
919 if user.company_id and user.company_id.partner_id:
920 if partner.id == user.company_id.partner_id.id:
923 if procurement.product_id.product_tmpl_id.type=='service':
924 res = res and self.check_produce_service(cr, uid, procurement, context)
926 res = res and self.check_produce_product(cr, uid, procurement, context)
931 def check_buy(self, cr, uid, ids):
932 user = self.pool.get('res.users').browse(cr, uid, uid)
933 for procurement in self.browse(cr, uid, ids):
934 if procurement.product_id.product_tmpl_id.supply_method<>'buy':
936 if not procurement.product_id.seller_ids:
937 cr.execute('update mrp_procurement set message=%s where id=%s', (_('No supplier defined for this product !'), procurement.id))
939 partner = procurement.product_id.seller_ids[0].name
940 if user.company_id and user.company_id.partner_id:
941 if partner.id == user.company_id.partner_id.id:
943 address_id = self.pool.get('res.partner').address_get(cr, uid, [partner.id], ['delivery'])['delivery']
945 cr.execute('update mrp_procurement set message=%s where id=%s', (_('No address defined for the supplier'), procurement.id))
949 def test_cancel(self, cr, uid, ids):
950 for record in self.browse(cr, uid, ids):
951 if record.move_id and record.move_id.state=='cancel':
955 def action_confirm(self, cr, uid, ids, context={}):
956 for procurement in self.browse(cr, uid, ids):
957 if procurement.product_id.type in ('product', 'consu'):
958 if not procurement.move_id:
959 source = procurement.location_id.id
960 if procurement.procure_method=='make_to_order':
961 source = procurement.product_id.product_tmpl_id.property_stock_procurement.id
962 id = self.pool.get('stock.move').create(cr, uid, {
963 'name': 'PROC:'+procurement.name,
964 'location_id': source,
965 'location_dest_id': procurement.location_id.id,
966 'product_id': procurement.product_id.id,
967 'product_qty':procurement.product_qty,
968 'product_uom': procurement.product_uom.id,
969 'date_planned': procurement.date_planned,
972 self.write(cr, uid, [procurement.id], {'move_id': id, 'close_move':1})
975 if procurement.procure_method=='make_to_stock' and procurement.move_id.state in ('waiting',):
976 id = self.pool.get('stock.move').write(cr, uid, [procurement.move_id.id], {'state':'confirmed'})
977 self.write(cr, uid, ids, {'state':'confirmed','message':''})
980 def action_move_assigned(self, cr, uid, ids):
981 self.write(cr, uid, ids, {'state':'running','message':_('from stock: products assigned.')})
984 def _check_make_to_stock_service(self, cr, uid, procurement, context={}):
987 def _check_make_to_stock_product(self, cr, uid, procurement, context={}):
989 if procurement.move_id:
990 id = procurement.move_id.id
991 if not (procurement.move_id.state in ('done','assigned','cancel')):
992 ok = ok and self.pool.get('stock.move').action_assign(cr, uid, [id])
993 cr.execute('select count(id) from stock_warehouse_orderpoint where product_id=%s', (procurement.product_id.id,))
994 if not cr.fetchone()[0]:
995 cr.execute('update mrp_procurement set message=%s where id=%s', (_('from stock and no minimum orderpoint rule defined'), procurement.id))
998 def action_produce_assign_service(self, cr, uid, ids, context={}):
999 for procurement in self.browse(cr, uid, ids):
1000 self.write(cr, uid, [procurement.id], {'state':'running'})
1003 def action_produce_assign_product(self, cr, uid, ids, context={}):
1005 company = self.pool.get('res.users').browse(cr, uid, uid, context).company_id
1006 for procurement in self.browse(cr, uid, ids):
1007 res_id = procurement.move_id.id
1008 loc_id = procurement.location_id.id
1009 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)
1010 newdate = newdate - DateTime.RelativeDateTime(days=company.manufacturing_lead)
1011 produce_id = self.pool.get('mrp.production').create(cr, uid, {
1012 'origin': procurement.origin,
1013 'product_id': procurement.product_id.id,
1014 'product_qty': procurement.product_qty,
1015 'product_uom': procurement.product_uom.id,
1016 'product_uos_qty': procurement.product_uos and procurement.product_uos_qty or False,
1017 'product_uos': procurement.product_uos and procurement.product_uos.id or False,
1018 'location_src_id': procurement.location_id.id,
1019 'location_dest_id': procurement.location_id.id,
1020 'bom_id': procurement.bom_id and procurement.bom_id.id or False,
1021 'date_planned': newdate.strftime('%Y-%m-%d %H:%M:%S'),
1022 'move_prod_id': res_id,
1024 self.write(cr, uid, [procurement.id], {'state':'running'})
1025 bom_result = self.pool.get('mrp.production').action_compute(cr, uid,
1026 [produce_id], properties=[x.id for x in procurement.property_ids])
1027 wf_service = netsvc.LocalService("workflow")
1028 wf_service.trg_validate(uid, 'mrp.production', produce_id, 'button_confirm', cr)
1031 def action_po_assign(self, cr, uid, ids, context={}):
1033 company = self.pool.get('res.users').browse(cr, uid, uid, context).company_id
1034 for procurement in self.browse(cr, uid, ids):
1035 res_id = procurement.move_id.id
1036 partner = procurement.product_id.seller_ids[0].name
1037 partner_id = partner.id
1038 address_id = self.pool.get('res.partner').address_get(cr, uid, [partner_id], ['delivery'])['delivery']
1039 pricelist_id = partner.property_product_pricelist_purchase.id
1041 uom_id = procurement.product_id.uom_po_id.id
1043 qty = self.pool.get('product.uom')._compute_qty(cr, uid, procurement.product_uom.id, procurement.product_qty, uom_id)
1044 if procurement.product_id.seller_ids[0].qty:
1045 qty=max(qty,procurement.product_id.seller_ids[0].qty)
1047 price = self.pool.get('product.pricelist').price_get(cr, uid, [pricelist_id], procurement.product_id.id, qty, False, {'uom': uom_id})[pricelist_id]
1049 newdate = DateTime.strptime(procurement.date_planned, '%Y-%m-%d %H:%M:%S')
1050 newdate = newdate - DateTime.RelativeDateTime(days=company.po_lead)
1051 context.update({'lang':partner.lang})
1052 product=self.pool.get('product.product').browse(cr,uid,procurement.product_id.id,context=context)
1055 'name': product.name,
1057 'product_id': procurement.product_id.id,
1058 'product_uom': uom_id,
1059 'price_unit': price,
1060 'date_planned': newdate.strftime('%Y-%m-%d %H:%M:%S'),
1061 'move_dest_id': res_id,
1062 'notes':product.description_purchase,
1065 taxes_ids = procurement.product_id.product_tmpl_id.supplier_taxes_id
1066 taxes = self.pool.get('account.fiscal.position').map_tax(cr, uid, partner.property_account_position, taxes_ids)
1068 'taxes_id':[(6,0,taxes)]
1070 purchase_id = self.pool.get('purchase.order').create(cr, uid, {
1071 'origin': procurement.origin,
1072 'partner_id': partner_id,
1073 'partner_address_id': address_id,
1074 'location_id': procurement.location_id.id,
1075 'pricelist_id': pricelist_id,
1076 'order_line': [(0,0,line)],
1077 'fiscal_position': partner.property_account_position and partner.property_account_position.id or False
1079 self.write(cr, uid, [procurement.id], {'state':'running', 'purchase_id':purchase_id})
1082 def action_cancel(self, cr, uid, ids):
1084 for proc in self.browse(cr, uid, ids):
1085 if proc.move_id and proc.move_id.state=='waiting':
1086 todo.append(proc.move_id.id)
1088 self.pool.get('stock.move').write(cr, uid, todo, {'state':'assigned'})
1089 self.write(cr, uid, ids, {'state':'cancel'})
1091 wf_service = netsvc.LocalService("workflow")
1093 wf_service.trg_trigger(uid, 'mrp.procurement', id, cr)
1097 def action_check_finnished(self, cr, uid, ids):
1098 return self.check_move_done(cr, uid, ids)
1100 def action_check(self, cr, uid, ids):
1102 for procurement in self.browse(cr, uid, ids):
1103 if procurement.move_id.state=='assigned' or procurement.move_id.state=='done':
1104 self.action_done(cr, uid, [procurement.id])
1108 def action_ready(self, cr, uid, ids):
1109 res = self.write(cr, uid, ids, {'state':'ready'})
1112 def action_done(self, cr, uid, ids):
1113 for procurement in self.browse(cr, uid, ids):
1114 if procurement.move_id:
1115 if procurement.close_move and (procurement.move_id.state <> 'done'):
1116 self.pool.get('stock.move').action_done(cr, uid, [procurement.move_id.id])
1117 res = self.write(cr, uid, ids, {'state':'done', 'date_close':time.strftime('%Y-%m-%d')})
1118 wf_service = netsvc.LocalService("workflow")
1120 wf_service.trg_trigger(uid, 'mrp.procurement', id, cr)
1123 def run_scheduler(self, cr, uid, automatic=False, use_new_cursor=False, context=None):
1125 use_new_cursor: False or the dbname
1129 self._procure_confirm(cr, uid, use_new_cursor=use_new_cursor, context=context)
1130 self._procure_orderpoint_confirm(cr, uid, automatic=automatic,\
1131 use_new_cursor=use_new_cursor, context=context)
1135 class stock_warehouse_orderpoint(osv.osv):
1136 _name = "stock.warehouse.orderpoint"
1137 _description = "Orderpoint minimum rule"
1139 'name': fields.char('Name', size=32, required=True),
1140 'active': fields.boolean('Active'),
1141 'logic': fields.selection([('max','Order to Max'),('price','Best price (not yet active!)')], 'Reordering Mode', required=True),
1142 'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse', required=True),
1143 'location_id': fields.many2one('stock.location', 'Location', required=True),
1144 'product_id': fields.many2one('product.product', 'Product', required=True, domain=[('type','=','product')]),
1145 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True ),
1146 'product_min_qty': fields.float('Min Quantity', required=True,
1147 help="When the virtual stock goes belong the Min Quantity, Open ERP generates "\
1148 "a procurement to bring the virtual stock to the Max Quantity."),
1149 'product_max_qty': fields.float('Max Quantity', required=True,
1150 help="When the virtual stock goes belong the Min Quantity, Open ERP generates "\
1151 "a procurement to bring the virtual stock to the Max Quantity."),
1152 'qty_multiple': fields.integer('Qty Multiple', required=True,
1153 help="The procurement quantity will by rounded up to this multiple."),
1154 'procurement_id': fields.many2one('mrp.procurement', 'Purchase Order')
1157 'active': lambda *a: 1,
1158 'logic': lambda *a: 'max',
1159 'qty_multiple': lambda *a: 1,
1160 'name': lambda x,y,z,c: x.pool.get('ir.sequence').get(y,z,'mrp.warehouse.orderpoint') or '',
1161 'product_uom': lambda sel, cr, uid, context: context.get('product_uom', False),
1163 def onchange_warehouse_id(self, cr, uid, ids, warehouse_id, context={}):
1165 w=self.pool.get('stock.warehouse').browse(cr,uid,warehouse_id, context)
1166 v = {'location_id':w.lot_stock_id.id}
1169 def onchange_product_id(self, cr, uid, ids, product_id, context={}):
1171 prod=self.pool.get('product.product').browse(cr,uid,product_id)
1172 v = {'product_uom':prod.uom_id.id}
1175 def copy(self, cr, uid, id, default=None,context={}):
1179 'name': self.pool.get('ir.sequence').get(cr, uid, 'mrp.warehouse.orderpoint') or '',
1181 return super(stock_warehouse_orderpoint, self).copy(cr, uid, id, default, context)
1182 stock_warehouse_orderpoint()
1185 class StockMove(osv.osv):
1186 _inherit = 'stock.move'
1188 'procurements': fields.one2many('mrp.procurement', 'move_id', 'Procurements'),
1190 def copy(self, cr, uid, id, default=None, context=None):
1191 default = default or {}
1192 default['procurements'] = []
1193 return super(StockMove, self).copy(cr, uid, id, default, context)
1195 def _action_explode(self, cr, uid, move, context={}):
1196 if move.product_id.supply_method=='produce' and move.product_id.procure_method=='make_to_order':
1197 bis = self.pool.get('mrp.bom').search(cr, uid, [
1198 ('product_id','=',move.product_id.id),
1199 ('bom_id','=',False),
1200 ('type','=','phantom')])
1202 factor = move.product_qty
1203 bom_point = self.pool.get('mrp.bom').browse(cr, uid, bis[0])
1204 res = self.pool.get('mrp.bom')._bom_explode(cr, uid, bom_point, factor, [])
1205 dest = move.product_id.product_tmpl_id.property_stock_production.id
1207 if move.state=='assigned':
1211 'picking_id': move.picking_id.id,
1212 'product_id': line['product_id'],
1213 'product_uom': line['product_uom'],
1214 'product_qty': line['product_qty'],
1215 'product_uos': line['product_uos'],
1216 'product_uos_qty': line['product_uos_qty'],
1217 'move_dest_id': move.id,
1219 'name': line['name'],
1220 'location_dest_id': dest,
1221 'move_history_ids': [(6,0,[move.id])],
1222 'move_history_ids2': [(6,0,[])],
1225 mid = self.pool.get('stock.move').copy(cr, uid, move.id, default=valdef)
1226 prodobj = self.pool.get('product.product').browse(cr, uid, line['product_id'], context=context)
1227 proc_id = self.pool.get('mrp.procurement').create(cr, uid, {
1228 'name': (move.picking_id.origin or ''),
1229 'origin': (move.picking_id.origin or ''),
1230 'date_planned': move.date_planned,
1231 'product_id': line['product_id'],
1232 'product_qty': line['product_qty'],
1233 'product_uom': line['product_uom'],
1234 'product_uos_qty': line['product_uos'] and line['product_uos_qty'] or False,
1235 'product_uos': line['product_uos'],
1236 'location_id': move.location_id.id,
1237 'procure_method': prodobj.procure_method,
1240 wf_service = netsvc.LocalService("workflow")
1241 wf_service.trg_validate(uid, 'mrp.procurement', proc_id, 'button_confirm', cr)
1242 self.pool.get('stock.move').write(cr, uid, [move.id], {
1243 'location_id': move.location_dest_id.id,
1244 'auto_validate': True,
1245 'picking_id': False,
1246 'location_id': dest,
1249 for m in self.pool.get('mrp.procurement').search(cr, uid, [('move_id','=',move.id)], context):
1250 wf_service = netsvc.LocalService("workflow")
1251 wf_service.trg_validate(uid, 'mrp.procurement', m, 'button_wait_done', cr)
1256 class StockPicking(osv.osv):
1257 _inherit = 'stock.picking'
1259 def test_finnished(self, cursor, user, ids):
1260 wf_service = netsvc.LocalService("workflow")
1261 res = super(StockPicking, self).test_finnished(cursor, user, ids)
1262 for picking in self.browse(cursor, user, ids):
1263 for move in picking.move_lines:
1264 if move.state == 'done' and move.procurements:
1265 for procurement in move.procurements:
1266 wf_service.trg_validate(user, 'mrp.procurement',
1267 procurement.id, 'button_check', cursor)
1271 # Explode picking by replacing phantom BoMs
1273 def action_explode(self, cr, uid, picks, *args):
1274 for move in self.pool.get('stock.move').browse(cr, uid, picks):
1275 self.pool.get('stock.move')._action_explode(cr, uid, move)
1280 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: