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
30 from tools.translate import _
32 #----------------------------------------------------------
34 #----------------------------------------------------------
35 # capacity_hour : capacity per hour. default: 1.0.
36 # Eg: If 5 concurrent operations at one time: capacity = 5 (because 5 employees)
37 # unit_per_cycle : how many units are produced for one cycle
39 # TODO: Work Center may be recursive ?
41 class mrp_workcenter(osv.osv):
42 _name = 'mrp.workcenter'
43 _description = 'Workcenter'
45 'name': fields.char('Workcenter Name', size=64, required=True),
46 'active': fields.boolean('Active'),
47 'type': fields.selection([('machine','Machine'),('hr','Human Resource'),('tool','Tool')], 'Type', required=True),
48 'code': fields.char('Code', size=16),
49 'timesheet_id': fields.many2one('hr.timesheet.group', 'Working Time', help="The normal working time of the workcenter."),
50 'note': fields.text('Description', help="Description of the workcenter. Explain here what's a cycle according to this workcenter."),
52 '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."),
54 'time_cycle': fields.float('Time for 1 cycle (hour)', help="Time in hours for doing one cycle."),
55 'time_start': fields.float('Time before prod.', help="Time in hours for the setup."),
56 'time_stop': fields.float('Time after prod.', help="Time in hours for the cleaning."),
57 'time_efficiency': fields.float('Time Efficiency', help="Factor that multiplies all times expressed in the workcenter."),
59 'costs_hour': fields.float('Cost per hour'),
60 'costs_hour_account_id': fields.many2one('account.analytic.account', 'Hour Account', domain=[('type','<>','view')],
61 help="Complete this only if you want automatic analytic accounting entries on production orders."),
62 'costs_cycle': fields.float('Cost per cycle'),
63 'costs_cycle_account_id': fields.many2one('account.analytic.account', 'Cycle Account', domain=[('type','<>','view')],
64 help="Complete this only if you want automatic analytic accounting entries on production orders."),
65 'costs_journal_id': fields.many2one('account.analytic.journal', 'Analytic Journal'),
66 'costs_general_account_id': fields.many2one('account.account', 'General Account', domain=[('type','<>','view')]),
69 'active': lambda *a: 1,
70 'type': lambda *a: 'machine',
71 'time_efficiency': lambda *a: 1.0,
72 'capacity_per_cycle': lambda *a: 1.0,
77 class mrp_property_group(osv.osv):
78 _name = 'mrp.property.group'
79 _description = 'Property Group'
81 'name': fields.char('Property Group', size=64, required=True),
82 'description': fields.text('Description'),
86 class mrp_property(osv.osv):
87 _name = 'mrp.property'
88 _description = 'Property'
90 'name': fields.char('Name', size=64, required=True),
91 'composition': fields.selection([('min','min'),('max','max'),('plus','plus')], 'Properties composition', required=True, help="Not used in computations, for information purpose only."),
92 'group_id': fields.many2one('mrp.property.group', 'Property Group', required=True),
93 'description': fields.text('Description'),
96 'composition': lambda *a: 'min',
100 class mrp_routing(osv.osv):
101 _name = 'mrp.routing'
102 _description = 'Routing'
104 'name': fields.char('Name', size=64, required=True),
105 'active': fields.boolean('Active'),
106 'code': fields.char('Code', size=8),
108 'note': fields.text('Description'),
109 'workcenter_lines': fields.one2many('mrp.routing.workcenter', 'routing_id', 'Workcenters'),
111 'location_id': fields.many2one('stock.location', 'Production Location',
112 help="Keep empty if you produce at the location where the finished products are needed." \
113 "Set a location if you produce at a fixed location. This can be a partner location " \
114 "if you subcontract the manufacturing operations."
118 'active': lambda *a: 1,
122 class mrp_routing_workcenter(osv.osv):
123 _name = 'mrp.routing.workcenter'
124 _description = 'Routing workcenter usage'
126 'workcenter_id': fields.many2one('mrp.workcenter', 'Workcenter', required=True),
127 'name': fields.char('Name', size=64, required=True),
128 'sequence': fields.integer('Sequence'),
129 'cycle_nbr': fields.float('Number of Cycle', required=True,
130 help="A cycle is defined in the workcenter definition."),
131 'hour_nbr': fields.float('Number of Hours', required=True),
132 'routing_id': fields.many2one('mrp.routing', 'Parent Routing', select=True),
133 'note': fields.text('Description')
136 'cycle_nbr': lambda *a: 1.0,
137 'hour_nbr': lambda *a: 0.0,
139 mrp_routing_workcenter()
141 class mrp_bom(osv.osv):
143 _description = 'Bill of Material'
144 def _child_compute(self, cr, uid, ids, name, arg, context={}):
146 for bom in self.browse(cr, uid, ids, context=context):
147 result[bom.id] = map(lambda x: x.id, bom.bom_lines)
150 ok = ((name=='child_complete_ids') and (bom.product_id.supply_method=='produce'))
151 if bom.type=='phantom' or ok:
152 sids = self.pool.get('mrp.bom').search(cr, uid, [('bom_id','=',False),('product_id','=',bom.product_id.id)])
154 bom2 = self.pool.get('mrp.bom').browse(cr, uid, sids[0], context=context)
155 result[bom.id] += map(lambda x: x.id, bom2.bom_lines)
157 def _compute_type(self, cr, uid, ids, field_name, arg, context):
158 res = dict(map(lambda x: (x,''), ids))
159 for line in self.browse(cr, uid, ids):
160 if line.type=='phantom' and not line.bom_id:
163 if line.bom_lines or line.type=='phantom':
165 if line.product_id.supply_method=='produce':
166 if line.product_id.procure_method=='make_to_stock':
167 res[line.id] = 'stock'
169 res[line.id] = 'order'
172 'name': fields.char('Name', size=64, required=True),
173 'code': fields.char('Code', size=16),
174 'active': fields.boolean('Active'),
175 'type': fields.selection([('normal','Normal BoM'),('phantom','Sets / Phantom')], 'BoM Type', required=True, help=
176 "Use a phantom bill of material in raw materials lines that have to be " \
177 "automatically computed in on eproduction order and not one per level." \
178 "If you put \"Phantom/Set\" at the root level of a bill of material " \
179 "it is considered as a set or pack: the products are replaced by the components " \
180 "between the sale order to the picking without going through the production order." \
181 "The normal BoM will generate one production order per BoM level."),
182 'method': fields.function(_compute_type, string='Method', method=True, type='selection', selection=[('',''),('stock','On Stock'),('order','On Order'),('set','Set / Pack')]),
183 'date_start': fields.date('Valid From', help="Validity of this BoM or component. Keep empty if it's always valid."),
184 'date_stop': fields.date('Valid Until', help="Validity of this BoM or component. Keep empty if it's always valid."),
185 'sequence': fields.integer('Sequence'),
186 'position': fields.char('Internal Ref.', size=64, help="Reference to a position in an external plan."),
187 'product_id': fields.many2one('product.product', 'Product', required=True),
188 'product_uos_qty': fields.float('Product UOS Qty'),
189 'product_uos': fields.many2one('product.uom', 'Product UOS'),
190 'product_qty': fields.float('Product Qty', required=True),
191 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True),
192 'product_rounding': fields.float('Product Rounding', help="Rounding applied on the product quantity. For integer only values, put 1.0"),
193 '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."),
194 'bom_lines': fields.one2many('mrp.bom', 'bom_id', 'BoM Lines'),
195 'bom_id': fields.many2one('mrp.bom', 'Parent BoM', ondelete='cascade', select=True),
196 '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."),
197 'property_ids': fields.many2many('mrp.property', 'mrp_bom_property_rel', 'bom_id','property_id', 'Properties'),
198 'revision_ids': fields.one2many('mrp.bom.revision', 'bom_id', 'BoM Revisions'),
199 'revision_type': fields.selection([('numeric','numeric indices'),('alpha','alphabetical indices')], 'indice type'),
200 'child_ids': fields.function(_child_compute,relation='mrp.bom', method=True, string="BoM Hyerarchy", type='many2many'),
201 'child_complete_ids': fields.function(_child_compute,relation='mrp.bom', method=True, string="BoM Hyerarchy", type='many2many')
204 'active': lambda *a: 1,
205 'product_efficiency': lambda *a: 1.0,
206 'product_qty': lambda *a: 1.0,
207 'product_rounding': lambda *a: 1.0,
208 'type': lambda *a: 'normal',
212 ('bom_qty_zero', 'CHECK (product_qty>0)', 'All product quantities must be greater than 0.\n' \
213 'You should install the mrp_subproduct module if you want to manage extra products on BoMs !'),
216 def _check_recursion(self, cr, uid, ids):
219 cr.execute('select distinct bom_id from mrp_bom where id in ('+','.join(map(str,ids))+')')
220 ids = filter(None, map(lambda x:x[0], cr.fetchall()))
226 (_check_recursion, 'Error ! You can not create recursive BoM.', ['parent_id'])
230 def onchange_product_id(self, cr, uid, ids, product_id, name, context={}):
232 prod=self.pool.get('product.product').browse(cr,uid,[product_id])[0]
233 v = {'product_uom':prod.uom_id.id}
235 v['name'] = prod.name
239 def _bom_find(self, cr, uid, product_id, product_uom, properties=[]):
241 # Why searching on BoM without parent ?
242 cr.execute('select id from mrp_bom where product_id=%s and bom_id is null order by sequence', (product_id,))
243 ids = map(lambda x: x[0], cr.fetchall())
246 for bom in self.pool.get('mrp.bom').browse(cr, uid, ids):
248 for prop_id in bom.property_ids:
249 if prop_id.id in properties:
251 if (prop>max_prop) or ((max_prop==0) and not result):
256 def _bom_explode(self, cr, uid, bom, factor, properties, addthis=False, level=0):
257 factor = factor / (bom.product_efficiency or 1.0)
258 factor = rounding(factor, bom.product_rounding)
259 if factor<bom.product_rounding:
260 factor = bom.product_rounding
264 if bom.type=='phantom' and not bom.bom_lines:
265 newbom = self._bom_find(cr, uid, bom.product_id.id, bom.product_uom.id, properties)
267 res = self._bom_explode(cr, uid, self.browse(cr, uid, [newbom])[0], factor*bom.product_qty, properties, addthis=True, level=level+10)
268 result = result + res[0]
269 result2 = result2 + res[1]
274 if addthis and not bom.bom_lines:
277 'name': bom.product_id.name,
278 'product_id': bom.product_id.id,
279 'product_qty': bom.product_qty * factor,
280 'product_uom': bom.product_uom.id,
281 'product_uos_qty': bom.product_uos and bom.product_uos_qty * factor or False,
282 'product_uos': bom.product_uos and bom.product_uos.id or False,
285 for wc_use in bom.routing_id.workcenter_lines:
286 wc = wc_use.workcenter_id
287 d, m = divmod(factor, wc_use.workcenter_id.capacity_per_cycle)
288 mult = (d + (m and 1.0 or 0.0))
289 cycle = mult * wc_use.cycle_nbr
291 'name': bom.routing_id.name,
292 'workcenter_id': wc.id,
293 'sequence': level+(wc_use.sequence or 0),
295 'hour': float(wc_use.hour_nbr*mult + (wc.time_start+wc.time_stop+cycle*wc.time_cycle) * (wc.time_efficiency or 1.0)),
297 for bom2 in bom.bom_lines:
298 res = self._bom_explode(cr, uid, bom2, factor, properties, addthis=True, level=level+10)
299 result = result + res[0]
300 result2 = result2 + res[1]
301 return result, result2
303 def set_indices(self, cr, uid, ids, context = {}):
304 if not ids or (ids and not ids[0]):
306 res = self.read(cr, uid, ids, ['revision_ids', 'revision_type'])
307 rev_ids = res[0]['revision_ids']
310 for rev_id in rev_ids:
311 if res[0]['revision_type'] == 'numeric':
312 self.pool.get('mrp.bom.revision').write(cr, uid, [rev_id], {'indice' : idx})
314 self.pool.get('mrp.bom.revision').write(cr, uid, [rev_id], {'indice' : "%c"%(idx+96,)})
320 class mrp_bom_revision(osv.osv):
321 _name = 'mrp.bom.revision'
322 _description = 'Bill of material revisions'
324 'name': fields.char('Modification name', size=64, required=True),
325 'description': fields.text('Description'),
326 'date': fields.date('Modification Date'),
327 'indice': fields.char('Revision', size=16),
328 'last_indice': fields.char('last indice', size=64),
329 'author_id': fields.many2one('res.users', 'Author'),
330 'bom_id': fields.many2one('mrp.bom', 'BoM', select=True),
334 'author_id': lambda x,y,z,c: z,
335 'date': lambda *a: time.strftime('%Y-%m-%d'),
343 return round(f / r) * r
345 class mrp_production(osv.osv):
346 _name = 'mrp.production'
347 _description = 'Production'
348 _date_name = 'date_planned'
350 def _get_sale_order(self,cr,uid,ids,field_name=False):
351 move_obj=self.pool.get('stock.move')
352 def get_parent_move(move_id):
353 move = move_obj.browse(cr,uid,move_id)
354 if move.move_dest_id:
355 return get_parent_move(move.move_dest_id.id)
357 productions=self.read(cr,uid,ids,['id','move_prod_id'])
359 for production in productions:
360 res[production['id']]=False
361 if production.get('move_prod_id',False):
362 parent_move_line=get_parent_move(production['move_prod_id'][0])
364 move = move_obj.browse(cr,uid,parent_move_line)
365 if field_name=='name':
366 res[production['id']]=move.sale_line_id and move.sale_line_id.order_id.name or False
367 if field_name=='client_order_ref':
368 res[production['id']]=move.sale_line_id and move.sale_line_id.order_id.client_order_ref or False
371 def _production_calc(self, cr, uid, ids, prop, unknow_none, context={}):
373 for prod in self.browse(cr, uid, ids, context=context):
378 for wc in prod.workcenter_lines:
379 result[prod.id]['hour_total'] += wc.hour
380 result[prod.id]['cycle_total'] += wc.cycle
383 def _production_date_end(self, cr, uid, ids, prop, unknow_none, context={}):
385 for prod in self.browse(cr, uid, ids, context=context):
386 result[prod.id] = prod.date_planned
389 def _production_date(self, cr, uid, ids, prop, unknow_none, context={}):
391 for prod in self.browse(cr, uid, ids, context=context):
392 result[prod.id] = prod.date_planned[:10]
395 def _sale_name_calc(self, cr, uid, ids, prop, unknow_none, unknow_dict):
396 return self._get_sale_order(cr,uid,ids,field_name='name')
398 def _sale_ref_calc(self, cr, uid, ids, prop, unknow_none, unknow_dict):
399 return self._get_sale_order(cr,uid,ids,field_name='client_order_ref')
402 'name': fields.char('Reference', size=64, required=True),
403 'origin': fields.char('Origin', size=64),
404 'priority': fields.selection([('0','Not urgent'),('1','Normal'),('2','Urgent'),('3','Very Urgent')], 'Priority'),
406 'product_id': fields.many2one('product.product', 'Product', required=True, domain=[('type','<>','service')]),
407 'product_qty': fields.float('Product Qty', required=True, states={'draft':[('readonly',False)]}, readonly=True),
408 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True, states={'draft':[('readonly',False)]}, readonly=True),
409 'product_uos_qty': fields.float('Product UoS Qty', states={'draft':[('readonly',False)]}, readonly=True),
410 'product_uos': fields.many2one('product.uom', 'Product UoS', states={'draft':[('readonly',False)]}, readonly=True),
412 'location_src_id': fields.many2one('stock.location', 'Raw Materials Location', required=True,
413 help="Location where the system will look for products used in raw materials."),
414 'location_dest_id': fields.many2one('stock.location', 'Finished Products Location', required=True,
415 help="Location where the system will stock the finished products."),
417 'date_planned_end': fields.function(_production_date_end, method=True, type='date', string='Scheduled End'),
418 'date_planned_date': fields.function(_production_date, method=True, type='date', string='Scheduled Date'),
419 'date_planned': fields.datetime('Scheduled date', required=True, select=1),
420 'date_start': fields.datetime('Start Date'),
421 'date_finnished': fields.datetime('End Date'),
423 'bom_id': fields.many2one('mrp.bom', 'Bill of Material', domain=[('bom_id','=',False)]),
424 'routing_id': fields.many2one('mrp.routing', string='Routing', on_delete='set null'),
426 'picking_id': fields.many2one('stock.picking', 'Packing list', readonly=True,
427 help="This is the internal picking list take bring the raw materials to the production plan."),
428 'move_prod_id': fields.many2one('stock.move', 'Move product', readonly=True),
429 'move_lines': fields.many2many('stock.move', 'mrp_production_move_ids', 'production_id', 'move_id', 'Products Consummed'),
431 'move_created_ids': fields.one2many('stock.move', 'production_id', 'Moves Created'),
432 'product_lines': fields.one2many('mrp.production.product.line', 'production_id', 'Scheduled goods'),
433 'workcenter_lines': fields.one2many('mrp.production.workcenter.line', 'production_id', 'Workcenters Utilisation'),
435 '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),
436 'hour_total': fields.function(_production_calc, method=True, type='float', string='Total Hours', multi='workorder'),
437 'cycle_total': fields.function(_production_calc, method=True, type='float', string='Total Cycles', multi='workorder'),
439 'sale_name': fields.function(_sale_name_calc, method=True, type='char', string='Sale Name'),
440 'sale_ref': fields.function(_sale_ref_calc, method=True, type='char', string='Sale Ref'),
443 'priority': lambda *a: '1',
444 'state': lambda *a: 'draft',
445 'date_planned': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
446 'product_qty': lambda *a: 1.0,
447 'name': lambda x,y,z,c: x.pool.get('ir.sequence').get(y,z,'mrp.production') or '/',
449 _order = 'date_planned asc, priority desc';
450 def unlink(self, cr, uid, ids, context=None):
451 productions = self.read(cr, uid, ids, ['state'])
453 for s in productions:
454 if s['state'] in ['draft','cancel']:
455 unlink_ids.append(s['id'])
457 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Production Order(s) which are in %s State!' % s['state']))
458 return osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
460 def copy(self, cr, uid, id, default=None,context=None):
464 'name': self.pool.get('ir.sequence').get(cr, uid, 'mrp.production'),
466 'move_created_ids': [],
469 return super(mrp_production, self).copy(cr, uid, id, default, context)
471 def location_id_change(self, cr, uid, ids, src, dest, context={}):
475 return {'value': {'location_dest_id': src}}
478 def product_id_change(self, cr, uid, ids, product):
481 res = self.pool.get('product.product').read(cr, uid, [product], ['uom_id'])[0]
482 uom = res['uom_id'] and res['uom_id'][0]
483 result = {'product_uom':uom}
484 return {'value':result}
486 def action_picking_except(self, cr, uid, ids):
487 self.write(cr, uid, ids, {'state':'picking_except'})
490 def action_compute(self, cr, uid, ids, properties=[]):
492 for production in self.browse(cr, uid, ids):
493 cr.execute('delete from mrp_production_product_line where production_id=%s', (production.id,))
494 cr.execute('delete from mrp_production_workcenter_line where production_id=%s', (production.id,))
495 bom_point = production.bom_id
496 bom_id = production.bom_id.id
498 bom_id = self.pool.get('mrp.bom')._bom_find(cr, uid, production.product_id.id, production.product_uom.id, properties)
500 bom_point = self.pool.get('mrp.bom').browse(cr, uid, bom_id)
501 routing_id = bom_point.routing_id.id or False
502 self.write(cr, uid, [production.id], {'bom_id': bom_id, 'routing_id': routing_id})
505 raise osv.except_osv(_('Error'), _("Couldn't find bill of material for product"))
507 #if bom_point.routing_id and bom_point.routing_id.location_id:
508 # self.write(cr, uid, [production.id], {'location_src_id': bom_point.routing_id.location_id.id})
510 factor = production.product_qty * production.product_uom.factor / bom_point.product_uom.factor
511 res = self.pool.get('mrp.bom')._bom_explode(cr, uid, bom_point, factor / bom_point.product_qty, properties)
515 line['production_id'] = production.id
516 self.pool.get('mrp.production.product.line').create(cr, uid, line)
517 for line in results2:
518 line['production_id'] = production.id
519 self.pool.get('mrp.production.workcenter.line').create(cr, uid, line)
522 def action_cancel(self, cr, uid, ids):
523 for production in self.browse(cr, uid, ids):
524 if production.move_created_ids:
525 self.pool.get('stock.move').action_cancel(cr, uid, [x.id for x in production.move_created_ids])
526 self.pool.get('stock.move').action_cancel(cr, uid, [x.id for x in production.move_lines])
527 self.write(cr, uid, ids, {'state':'cancel'}) #,'move_lines':[(6,0,[])]})
530 #XXX: may be a bug here; lot_lines are unreserved for a few seconds;
531 # between the end of the picking list and the call to this function
532 def action_ready(self, cr, uid, ids):
533 self.write(cr, uid, ids, {'state':'ready'})
534 for production in self.browse(cr, uid, ids):
535 if production.move_prod_id:
536 self.pool.get('stock.move').write(cr, uid, [production.move_prod_id.id],
537 {'location_id':production.location_dest_id.id})
540 #TODO Review materials in function in_prod and prod_end.
541 def action_production_end(self, cr, uid, ids):
543 for production in self.browse(cr, uid, ids):
544 for res in production.move_lines:
545 for move in production.move_created_ids:
546 #XXX must use the orm
547 cr.execute('INSERT INTO stock_move_history_ids \
548 (parent_id, child_id) VALUES (%s,%s)',
550 move_ids.append(res.id)
551 vals= {'state':'confirmed'}
552 new_moves = [x.id for x in production.move_created_ids]
553 self.pool.get('stock.move').write(cr, uid, new_moves, vals)
554 if not production.date_finnished:
555 self.write(cr, uid, [production.id],
556 {'date_finnished': time.strftime('%Y-%m-%d %H:%M:%S')})
557 self.pool.get('stock.move').check_assign(cr, uid, new_moves)
558 self.pool.get('stock.move').action_done(cr, uid, new_moves)
559 self._costs_generate(cr, uid, production)
560 self.pool.get('stock.move').action_done(cr, uid, move_ids)
561 self.write(cr, uid, ids, {'state': 'done'})
564 def _costs_generate(self, cr, uid, production):
566 for wc_line in production.workcenter_lines:
567 wc = wc_line.workcenter_id
568 if wc.costs_journal_id and wc.costs_general_account_id:
569 value = wc_line.hour * wc.costs_hour
570 account = wc.costs_hour_account_id.id
571 if value and account:
573 self.pool.get('account.analytic.line').create(cr, uid, {
574 'name': wc_line.name+' (H)',
576 'account_id': account,
577 'general_account_id': wc.costs_general_account_id.id,
578 'journal_id': wc.costs_journal_id.id,
581 if wc.costs_journal_id and wc.costs_general_account_id:
582 value = wc_line.cycle * wc.costs_cycle
583 account = wc.costs_cycle_account_id.id
584 if value and account:
586 self.pool.get('account.analytic.line').create(cr, uid, {
587 'name': wc_line.name+' (C)',
589 'account_id': account,
590 'general_account_id': wc.costs_general_account_id.id,
591 'journal_id': wc.costs_journal_id.id,
596 def action_in_production(self, cr, uid, ids):
598 for production in self.browse(cr, uid, ids):
599 for res in production.move_lines:
600 move_ids.append(res.id)
601 if not production.date_start:
602 self.write(cr, uid, [production.id],
603 {'date_start': time.strftime('%Y-%m-%d %H:%M:%S')})
604 self.pool.get('stock.move').action_done(cr, uid, move_ids)
605 self.write(cr, uid, ids, {'state': 'in_production'})
608 def test_if_product(self, cr, uid, ids):
610 for production in self.browse(cr, uid, ids):
611 if not production.product_lines:
612 if not self.action_compute(cr, uid, [production.id]):
616 def _get_auto_picking(self, cr, uid, production):
619 def action_confirm(self, cr, uid, ids):
622 for production in self.browse(cr, uid, ids):
623 if not production.product_lines:
624 self.action_compute(cr, uid, [production.id])
625 production = self.browse(cr, uid, [production.id])[0]
627 pick_type = 'internal'
629 if production.bom_id.routing_id and production.bom_id.routing_id.location_id:
630 routing_loc = production.bom_id.routing_id.location_id
631 if routing_loc.usage<>'internal':
633 address_id = routing_loc.address_id and routing_loc.address_id.id or False
634 routing_loc = routing_loc.id
635 picking_id = self.pool.get('stock.picking').create(cr, uid, {
636 'origin': (production.origin or '').split(':')[0] +':'+production.name,
640 'address_id': address_id,
641 'auto_picking': self._get_auto_picking(cr, uid, production),
644 source = production.product_id.product_tmpl_id.property_stock_production.id
646 'name':'PROD:'+production.name,
647 'date_planned': production.date_planned,
648 'product_id': production.product_id.id,
649 'product_qty': production.product_qty,
650 'product_uom': production.product_uom.id,
651 'product_uos_qty': production.product_uos and production.product_uos_qty or False,
652 'product_uos': production.product_uos and production.product_uos.id or False,
653 'location_id': source,
654 'location_dest_id': production.location_dest_id.id,
655 'move_dest_id': production.move_prod_id.id,
658 res_final_id = self.pool.get('stock.move').create(cr, uid, data)
660 self.write(cr, uid, [production.id], {'move_created_ids': [(6, 0, [res_final_id])]})
662 for line in production.product_lines:
664 newdate = production.date_planned
665 if line.product_id.type in ('product', 'consu'):
666 res_dest_id = self.pool.get('stock.move').create(cr, uid, {
667 'name':'PROD:'+production.name,
668 'date_planned': production.date_planned,
669 'product_id': line.product_id.id,
670 'product_qty': line.product_qty,
671 'product_uom': line.product_uom.id,
672 'product_uos_qty': line.product_uos and line.product_uos_qty or False,
673 'product_uos': line.product_uos and line.product_uos.id or False,
674 'location_id': routing_loc or production.location_src_id.id,
675 'location_dest_id': source,
676 'move_dest_id': res_final_id,
679 moves.append(res_dest_id)
680 move_id = self.pool.get('stock.move').create(cr, uid, {
681 'name':'PROD:'+production.name,
682 'picking_id':picking_id,
683 'product_id': line.product_id.id,
684 'product_qty': line.product_qty,
685 'product_uom': line.product_uom.id,
686 'product_uos_qty': line.product_uos and line.product_uos_qty or False,
687 'product_uos': line.product_uos and line.product_uos.id or False,
688 'date_planned': newdate,
689 'move_dest_id': res_dest_id,
690 'location_id': production.location_src_id.id,
691 'location_dest_id': routing_loc or production.location_src_id.id,
694 proc_id = self.pool.get('mrp.procurement').create(cr, uid, {
695 'name': (production.origin or '').split(':')[0] + ':' + production.name,
696 'origin': (production.origin or '').split(':')[0] + ':' + production.name,
697 'date_planned': newdate,
698 'product_id': line.product_id.id,
699 'product_qty': line.product_qty,
700 'product_uom': line.product_uom.id,
701 'product_uos_qty': line.product_uos and line.product_qty or False,
702 'product_uos': line.product_uos and line.product_uos.id or False,
703 'location_id': production.location_src_id.id,
704 'procure_method': line.product_id.procure_method,
707 wf_service = netsvc.LocalService("workflow")
708 wf_service.trg_validate(uid, 'mrp.procurement', proc_id, 'button_confirm', cr)
709 proc_ids.append(proc_id)
710 wf_service = netsvc.LocalService("workflow")
711 wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
712 self.write(cr, uid, [production.id], {'picking_id':picking_id, 'move_lines': [(6,0,moves)], 'state':'confirmed'})
715 def force_production(self, cr, uid, ids, *args):
716 pick_obj = self.pool.get('stock.picking')
717 pick_obj.force_assign(cr, uid, [prod.picking_id.id for prod in self.browse(cr, uid, ids)])
723 class stock_move(osv.osv):
725 _inherit = 'stock.move'
727 'production_id': fields.many2one('mrp.production', 'Production', select=True),
731 class mrp_production_workcenter_line(osv.osv):
732 _name = 'mrp.production.workcenter.line'
733 _description = 'Work Orders'
736 'name': fields.char('Work Order', size=64, required=True),
737 'workcenter_id': fields.many2one('mrp.workcenter', 'Workcenter', required=True),
738 'cycle': fields.float('Nbr of cycle', digits=(16,2)),
739 'hour': fields.float('Nbr of hour', digits=(16,2)),
740 'sequence': fields.integer('Sequence', required=True),
741 'production_id': fields.many2one('mrp.production', 'Production Order', select=True, ondelete='cascade'),
744 'sequence': lambda *a: 1,
745 'hour': lambda *a: 0,
746 'cycle': lambda *a: 0,
748 mrp_production_workcenter_line()
750 class mrp_production_product_line(osv.osv):
751 _name = 'mrp.production.product.line'
752 _description = 'Production scheduled products'
754 'name': fields.char('Name', size=64, required=True),
755 'product_id': fields.many2one('product.product', 'Product', required=True),
756 'product_qty': fields.float('Product Qty', required=True),
757 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True),
758 'product_uos_qty': fields.float('Product UOS Qty'),
759 'product_uos': fields.many2one('product.uom', 'Product UOS'),
760 'production_id': fields.many2one('mrp.production', 'Production Order', select=True),
762 mrp_production_product_line()
764 # ------------------------------------------------------------------
766 # ------------------------------------------------------------------
768 # Produce, Buy or Find products and place a move
769 # then wizard for picking lists & move
771 class mrp_procurement(osv.osv):
772 _name = "mrp.procurement"
773 _description = "Procurement"
775 'name': fields.char('Name', size=64, required=True),
776 'origin': fields.char('Origin', size=64,
777 help="Reference of the document that created this procurement.\n"
778 "This is automatically completed by Open ERP."),
779 'priority': fields.selection([('0','Not urgent'),('1','Normal'),('2','Urgent'),('3','Very Urgent')], 'Priority', required=True),
780 'date_planned': fields.datetime('Scheduled date', required=True),
781 'date_close': fields.datetime('Date Closed'),
782 'product_id': fields.many2one('product.product', 'Product', required=True),
783 'product_qty': fields.float('Quantity', required=True, states={'draft':[('readonly',False)]}, readonly=True),
784 'product_uom': fields.many2one('product.uom', 'Product UoM', required=True, states={'draft':[('readonly',False)]}, readonly=True),
785 'product_uos_qty': fields.float('UoS Quantity', states={'draft':[('readonly',False)]}, readonly=True),
786 'product_uos': fields.many2one('product.uom', 'Product UoS', states={'draft':[('readonly',False)]}, readonly=True),
787 'move_id': fields.many2one('stock.move', 'Reservation', ondelete='set null'),
789 'bom_id': fields.many2one('mrp.bom', 'BoM', ondelete='cascade', select=True),
791 'close_move': fields.boolean('Close Move at end', required=True),
792 'location_id': fields.many2one('stock.location', 'Location', required=True),
793 'procure_method': fields.selection([('make_to_stock','from stock'),('make_to_order','on order')], 'Procurement Method', states={'draft':[('readonly',False)], 'confirmed':[('readonly',False)]},
794 readonly=True, required=True, help="If you encode manually a procurement, you probably want to use" \
795 " a make to order method."),
797 'purchase_id': fields.many2one('purchase.order', 'Purchase Order'),
798 'note': fields.text('Note'),
800 'property_ids': fields.many2many('mrp.property', 'mrp_procurement_property_rel', 'procurement_id','property_id', 'Properties'),
802 'message': fields.char('Latest error', size=64),
803 'state': fields.selection([
805 ('confirmed','Confirmed'),
806 ('exception','Exception'),
807 ('running','Running'),
811 ('waiting','Waiting')], 'Status', required=True),
812 'note' : fields.text('Note'),
815 'state': lambda *a: 'draft',
816 'priority': lambda *a: '1',
817 'date_planned': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
818 'close_move': lambda *a: 0,
819 'procure_method': lambda *a: 'make_to_order',
822 def unlink(self, cr, uid, ids, context=None):
823 procurements = self.read(cr, uid, ids, ['state'])
825 for s in procurements:
826 if s['state'] in ['draft','cancel']:
827 unlink_ids.append(s['id'])
829 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Procurement Order(s) which are in %s State!' % s['state']))
830 return osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
832 def onchange_product_id(self, cr, uid, ids, product_id, context={}):
834 w=self.pool.get('product.product').browse(cr,uid,product_id, context)
836 'product_uom':w.uom_id.id,
837 'product_uos':w.uos_id and w.uos_id.id or w.uom_id.id
842 def check_product(self, cr, uid, ids):
843 for procurement in self.browse(cr, uid, ids):
844 if procurement.product_id.type in ('product', 'consu'):
848 def check_move_cancel(self, cr, uid, ids, context={}):
851 for procurement in self.browse(cr, uid, ids, context):
852 if procurement.move_id:
854 if not procurement.move_id.state=='cancel':
858 def check_move_done(self, cr, uid, ids, context={}):
860 for proc in self.browse(cr, uid, ids, context):
862 if not proc.move_id.state=='done':
867 # This method may be overrided by objects that override mrp.procurment
868 # for computing their own purpose
870 def _quantity_compute_get(self, cr, uid, proc, context={}):
871 if proc.product_id.type=='product':
872 if proc.move_id.product_uos:
873 return proc.move_id.product_uos_qty
876 def _uom_compute_get(self, cr, uid, proc, context={}):
877 if proc.product_id.type=='product':
878 if proc.move_id.product_uos:
879 return proc.move_id.product_uos.id
883 # Return the quantity of product shipped/produced/served, wich may be
884 # different from the planned quantity
886 def quantity_get(self, cr, uid, id, context={}):
887 proc = self.browse(cr, uid, id, context)
888 result = self._quantity_compute_get(cr, uid, proc, context)
890 result = proc.product_qty
893 def uom_get(self, cr, uid, id, context=None):
894 proc = self.browse(cr, uid, id, context)
895 result = self._uom_compute_get(cr, uid, proc, context)
897 result = proc.product_uom.id
900 def check_waiting(self, cr, uid, ids, context=[]):
901 for procurement in self.browse(cr, uid, ids, context=context):
902 if procurement.move_id and procurement.move_id.state=='auto':
906 def check_produce_service(self, cr, uid, procurement, context=[]):
909 def check_produce_product(self, cr, uid, procurement, context=[]):
910 properties = [x.id for x in procurement.property_ids]
911 bom_id = self.pool.get('mrp.bom')._bom_find(cr, uid, procurement.product_id.id, procurement.product_uom.id, properties)
913 cr.execute('update mrp_procurement set message=%s where id=%s', (_('No BoM defined for this product !'), procurement.id))
917 def check_make_to_stock(self, cr, uid, ids, context={}):
919 for procurement in self.browse(cr, uid, ids, context=context):
920 if procurement.product_id.type=='service':
921 ok = ok and self._check_make_to_stock_service(cr, uid, procurement, context)
923 ok = ok and self._check_make_to_stock_product(cr, uid, procurement, context)
926 def check_produce(self, cr, uid, ids, context={}):
928 user = self.pool.get('res.users').browse(cr, uid, uid)
929 for procurement in self.browse(cr, uid, ids):
930 if procurement.product_id.product_tmpl_id.supply_method<>'produce':
931 if procurement.product_id.seller_ids:
932 partner = procurement.product_id.seller_ids[0].name
933 if user.company_id and user.company_id.partner_id:
934 if partner.id == user.company_id.partner_id.id:
937 if procurement.product_id.product_tmpl_id.type=='service':
938 res = res and self.check_produce_service(cr, uid, procurement, context)
940 res = res and self.check_produce_product(cr, uid, procurement, context)
945 def check_buy(self, cr, uid, ids):
946 user = self.pool.get('res.users').browse(cr, uid, uid)
947 for procurement in self.browse(cr, uid, ids):
948 if procurement.product_id.product_tmpl_id.supply_method<>'buy':
950 if not procurement.product_id.seller_ids:
951 cr.execute('update mrp_procurement set message=%s where id=%s', (_('No supplier defined for this product !'), procurement.id))
953 partner = procurement.product_id.seller_ids[0].name
954 if user.company_id and user.company_id.partner_id:
955 if partner.id == user.company_id.partner_id.id:
957 address_id = self.pool.get('res.partner').address_get(cr, uid, [partner.id], ['delivery'])['delivery']
959 cr.execute('update mrp_procurement set message=%s where id=%s', (_('No address defined for the supplier'), procurement.id))
963 def test_cancel(self, cr, uid, ids):
964 for record in self.browse(cr, uid, ids):
965 if record.move_id and record.move_id.state=='cancel':
969 def action_confirm(self, cr, uid, ids, context={}):
970 for procurement in self.browse(cr, uid, ids):
971 if procurement.product_id.type in ('product', 'consu'):
972 if not procurement.move_id:
973 source = procurement.location_id.id
974 if procurement.procure_method=='make_to_order':
975 source = procurement.product_id.product_tmpl_id.property_stock_procurement.id
976 id = self.pool.get('stock.move').create(cr, uid, {
977 'name': 'PROC:'+procurement.name,
978 'location_id': source,
979 'location_dest_id': procurement.location_id.id,
980 'product_id': procurement.product_id.id,
981 'product_qty':procurement.product_qty,
982 'product_uom': procurement.product_uom.id,
983 'date_planned': procurement.date_planned,
986 self.write(cr, uid, [procurement.id], {'move_id': id, 'close_move':1})
989 if procurement.procure_method=='make_to_stock' and procurement.move_id.state in ('waiting',):
990 id = self.pool.get('stock.move').write(cr, uid, [procurement.move_id.id], {'state':'confirmed'})
991 self.write(cr, uid, ids, {'state':'confirmed','message':''})
994 def action_move_assigned(self, cr, uid, ids):
995 self.write(cr, uid, ids, {'state':'running','message':_('from stock: products assigned.')})
998 def _check_make_to_stock_service(self, cr, uid, procurement, context={}):
1001 def _check_make_to_stock_product(self, cr, uid, procurement, context={}):
1003 if procurement.move_id:
1004 id = procurement.move_id.id
1005 if not (procurement.move_id.state in ('done','assigned','cancel')):
1006 ok = ok and self.pool.get('stock.move').action_assign(cr, uid, [id])
1007 cr.execute('select count(id) from stock_warehouse_orderpoint where product_id=%s', (procurement.product_id.id,))
1008 if not cr.fetchone()[0]:
1009 cr.execute('update mrp_procurement set message=%s where id=%s', (_('from stock and no minimum orderpoint rule defined'), procurement.id))
1012 def action_produce_assign_service(self, cr, uid, ids, context={}):
1013 for procurement in self.browse(cr, uid, ids):
1014 self.write(cr, uid, [procurement.id], {'state':'running'})
1017 def action_produce_assign_product(self, cr, uid, ids, context={}):
1019 company = self.pool.get('res.users').browse(cr, uid, uid, context).company_id
1020 for procurement in self.browse(cr, uid, ids):
1021 res_id = procurement.move_id.id
1022 loc_id = procurement.location_id.id
1023 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)
1024 newdate = newdate - DateTime.RelativeDateTime(days=company.manufacturing_lead)
1025 produce_id = self.pool.get('mrp.production').create(cr, uid, {
1026 'origin': procurement.origin,
1027 'product_id': procurement.product_id.id,
1028 'product_qty': procurement.product_qty,
1029 'product_uom': procurement.product_uom.id,
1030 'product_uos_qty': procurement.product_uos and procurement.product_uos_qty or False,
1031 'product_uos': procurement.product_uos and procurement.product_uos.id or False,
1032 'location_src_id': procurement.location_id.id,
1033 'location_dest_id': procurement.location_id.id,
1034 'bom_id': procurement.bom_id and procurement.bom_id.id or False,
1035 'date_planned': newdate.strftime('%Y-%m-%d %H:%M:%S'),
1036 'move_prod_id': res_id,
1038 self.write(cr, uid, [procurement.id], {'state':'running'})
1039 bom_result = self.pool.get('mrp.production').action_compute(cr, uid,
1040 [produce_id], properties=[x.id for x in procurement.property_ids])
1041 wf_service = netsvc.LocalService("workflow")
1042 wf_service.trg_validate(uid, 'mrp.production', produce_id, 'button_confirm', cr)
1043 self.pool.get('stock.move').write(cr, uid, [res_id],
1044 {'location_id':procurement.location_id.id})
1047 def action_po_assign(self, cr, uid, ids, context={}):
1049 company = self.pool.get('res.users').browse(cr, uid, uid, context).company_id
1050 for procurement in self.browse(cr, uid, ids):
1051 res_id = procurement.move_id.id
1052 partner = procurement.product_id.seller_ids[0].name
1053 partner_id = partner.id
1054 address_id = self.pool.get('res.partner').address_get(cr, uid, [partner_id], ['delivery'])['delivery']
1055 pricelist_id = partner.property_product_pricelist_purchase.id
1057 uom_id = procurement.product_id.uom_po_id.id
1059 qty = self.pool.get('product.uom')._compute_qty(cr, uid, procurement.product_uom.id, procurement.product_qty, uom_id)
1060 if procurement.product_id.seller_ids[0].qty:
1061 qty=max(qty,procurement.product_id.seller_ids[0].qty)
1063 price = self.pool.get('product.pricelist').price_get(cr, uid, [pricelist_id], procurement.product_id.id, qty, False, {'uom': uom_id})[pricelist_id]
1065 newdate = DateTime.strptime(procurement.date_planned, '%Y-%m-%d %H:%M:%S')
1066 newdate = newdate - DateTime.RelativeDateTime(days=company.po_lead)
1067 newdate = newdate - procurement.product_id.seller_ids[0].delay
1069 context.update({'lang':partner.lang})
1070 product=self.pool.get('product.product').browse(cr,uid,procurement.product_id.id,context=context)
1073 'name': product.name,
1075 'product_id': procurement.product_id.id,
1076 'product_uom': uom_id,
1077 'price_unit': price,
1078 'date_planned': newdate.strftime('%Y-%m-%d %H:%M:%S'),
1079 'move_dest_id': res_id,
1080 'notes':product.description_purchase,
1083 taxes_ids = procurement.product_id.product_tmpl_id.supplier_taxes_id
1084 taxes = self.pool.get('account.fiscal.position').map_tax(cr, uid, partner.property_account_position, taxes_ids)
1086 'taxes_id':[(6,0,taxes)]
1088 purchase_id = self.pool.get('purchase.order').create(cr, uid, {
1089 'origin': procurement.origin,
1090 'partner_id': partner_id,
1091 'partner_address_id': address_id,
1092 'location_id': procurement.location_id.id,
1093 'pricelist_id': pricelist_id,
1094 'order_line': [(0,0,line)],
1095 'fiscal_position': partner.property_account_position and partner.property_account_position.id or False
1097 self.write(cr, uid, [procurement.id], {'state':'running', 'purchase_id':purchase_id})
1100 def action_cancel(self, cr, uid, ids):
1103 for proc in self.browse(cr, uid, ids):
1105 if proc.move_id.state not in ('done','cancel'):
1106 todo2.append(proc.move_id.id)
1108 if proc.move_id and proc.move_id.state=='waiting':
1109 todo.append(proc.move_id.id)
1111 self.pool.get('stock.move').action_cancel(cr, uid, todo2)
1113 self.pool.get('stock.move').write(cr, uid, todo, {'state':'assigned'})
1114 self.write(cr, uid, ids, {'state':'cancel'})
1115 wf_service = netsvc.LocalService("workflow")
1117 wf_service.trg_trigger(uid, 'mrp.procurement', id, cr)
1120 def action_check_finnished(self, cr, uid, ids):
1121 return self.check_move_done(cr, uid, ids)
1123 def action_check(self, cr, uid, ids):
1125 for procurement in self.browse(cr, uid, ids):
1126 if procurement.move_id.state=='assigned' or procurement.move_id.state=='done':
1127 self.action_done(cr, uid, [procurement.id])
1131 def action_ready(self, cr, uid, ids):
1132 res = self.write(cr, uid, ids, {'state':'ready'})
1135 def action_done(self, cr, uid, ids):
1136 for procurement in self.browse(cr, uid, ids):
1137 if procurement.move_id:
1138 if procurement.close_move and (procurement.move_id.state <> 'done'):
1139 self.pool.get('stock.move').action_done(cr, uid, [procurement.move_id.id])
1140 res = self.write(cr, uid, ids, {'state':'done', 'date_close':time.strftime('%Y-%m-%d')})
1141 wf_service = netsvc.LocalService("workflow")
1143 wf_service.trg_trigger(uid, 'mrp.procurement', id, cr)
1146 def run_scheduler(self, cr, uid, automatic=False, use_new_cursor=False, context=None):
1148 use_new_cursor: False or the dbname
1152 self._procure_confirm(cr, uid, use_new_cursor=use_new_cursor, context=context)
1153 self._procure_orderpoint_confirm(cr, uid, automatic=automatic,\
1154 use_new_cursor=use_new_cursor, context=context)
1158 class stock_warehouse_orderpoint(osv.osv):
1159 _name = "stock.warehouse.orderpoint"
1160 _description = "Orderpoint minimum rule"
1162 'name': fields.char('Name', size=32, required=True),
1163 'active': fields.boolean('Active'),
1164 'logic': fields.selection([('max','Order to Max'),('price','Best price (not yet active!)')], 'Reordering Mode', required=True),
1165 'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse', required=True),
1166 'location_id': fields.many2one('stock.location', 'Location', required=True),
1167 'product_id': fields.many2one('product.product', 'Product', required=True, domain=[('type','=','product')]),
1168 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True ),
1169 'product_min_qty': fields.float('Min Quantity', required=True,
1170 help="When the virtual stock goes belong the Min Quantity, Open ERP generates "\
1171 "a procurement to bring the virtual stock to the Max Quantity."),
1172 'product_max_qty': fields.float('Max Quantity', required=True,
1173 help="When the virtual stock goes belong the Min Quantity, Open ERP generates "\
1174 "a procurement to bring the virtual stock to the Max Quantity."),
1175 'qty_multiple': fields.integer('Qty Multiple', required=True,
1176 help="The procurement quantity will by rounded up to this multiple."),
1177 'procurement_id': fields.many2one('mrp.procurement', 'Purchase Order')
1180 'active': lambda *a: 1,
1181 'logic': lambda *a: 'max',
1182 'qty_multiple': lambda *a: 1,
1183 'name': lambda x,y,z,c: x.pool.get('ir.sequence').get(y,z,'mrp.warehouse.orderpoint') or '',
1184 'product_uom': lambda sel, cr, uid, context: context.get('product_uom', False),
1186 def onchange_warehouse_id(self, cr, uid, ids, warehouse_id, context={}):
1188 w=self.pool.get('stock.warehouse').browse(cr,uid,warehouse_id, context)
1189 v = {'location_id':w.lot_stock_id.id}
1192 def onchange_product_id(self, cr, uid, ids, product_id, context={}):
1194 prod=self.pool.get('product.product').browse(cr,uid,product_id)
1195 v = {'product_uom':prod.uom_id.id}
1198 def copy(self, cr, uid, id, default=None,context={}):
1202 'name': self.pool.get('ir.sequence').get(cr, uid, 'mrp.warehouse.orderpoint') or '',
1204 return super(stock_warehouse_orderpoint, self).copy(cr, uid, id, default, context)
1205 stock_warehouse_orderpoint()
1208 class StockMove(osv.osv):
1209 _inherit = 'stock.move'
1211 'procurements': fields.one2many('mrp.procurement', 'move_id', 'Procurements'),
1213 def copy(self, cr, uid, id, default=None, context=None):
1214 default = default or {}
1215 default['procurements'] = []
1216 return super(StockMove, self).copy(cr, uid, id, default, context)
1218 def _action_explode(self, cr, uid, move, context={}):
1219 if move.product_id.supply_method=='produce' and move.product_id.procure_method=='make_to_order':
1220 bis = self.pool.get('mrp.bom').search(cr, uid, [
1221 ('product_id','=',move.product_id.id),
1222 ('bom_id','=',False),
1223 ('type','=','phantom')])
1225 factor = move.product_qty
1226 bom_point = self.pool.get('mrp.bom').browse(cr, uid, bis[0])
1227 res = self.pool.get('mrp.bom')._bom_explode(cr, uid, bom_point, factor, [])
1228 dest = move.product_id.product_tmpl_id.property_stock_production.id
1230 if move.state=='assigned':
1234 'picking_id': move.picking_id.id,
1235 'product_id': line['product_id'],
1236 'product_uom': line['product_uom'],
1237 'product_qty': line['product_qty'],
1238 'product_uos': line['product_uos'],
1239 'product_uos_qty': line['product_uos_qty'],
1240 'move_dest_id': move.id,
1242 'name': line['name'],
1243 'location_dest_id': dest,
1244 'move_history_ids': [(6,0,[move.id])],
1245 'move_history_ids2': [(6,0,[])],
1248 mid = self.pool.get('stock.move').copy(cr, uid, move.id, default=valdef)
1249 prodobj = self.pool.get('product.product').browse(cr, uid, line['product_id'], context=context)
1250 proc_id = self.pool.get('mrp.procurement').create(cr, uid, {
1251 'name': (move.picking_id.origin or ''),
1252 'origin': (move.picking_id.origin or ''),
1253 'date_planned': move.date_planned,
1254 'product_id': line['product_id'],
1255 'product_qty': line['product_qty'],
1256 'product_uom': line['product_uom'],
1257 'product_uos_qty': line['product_uos'] and line['product_uos_qty'] or False,
1258 'product_uos': line['product_uos'],
1259 'location_id': move.location_id.id,
1260 'procure_method': prodobj.procure_method,
1263 wf_service = netsvc.LocalService("workflow")
1264 wf_service.trg_validate(uid, 'mrp.procurement', proc_id, 'button_confirm', cr)
1265 self.pool.get('stock.move').write(cr, uid, [move.id], {
1266 'location_id': move.location_dest_id.id,
1267 'auto_validate': True,
1268 'picking_id': False,
1269 'location_id': dest,
1272 for m in self.pool.get('mrp.procurement').search(cr, uid, [('move_id','=',move.id)], context):
1273 wf_service = netsvc.LocalService("workflow")
1274 wf_service.trg_validate(uid, 'mrp.procurement', m, 'button_wait_done', cr)
1279 class StockPicking(osv.osv):
1280 _inherit = 'stock.picking'
1282 def test_finnished(self, cursor, user, ids):
1283 wf_service = netsvc.LocalService("workflow")
1284 res = super(StockPicking, self).test_finnished(cursor, user, ids)
1285 for picking in self.browse(cursor, user, ids):
1286 for move in picking.move_lines:
1287 if move.state == 'done' and move.procurements:
1288 for procurement in move.procurements:
1289 wf_service.trg_validate(user, 'mrp.procurement',
1290 procurement.id, 'button_check', cursor)
1294 # Explode picking by replacing phantom BoMs
1296 def action_explode(self, cr, uid, picks, *args):
1297 for move in self.pool.get('stock.move').browse(cr, uid, picks):
1298 self.pool.get('stock.move')._action_explode(cr, uid, move)
1303 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: