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(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[:10]
389 def _sale_name_calc(self, cr, uid, ids, prop, unknow_none, unknow_dict):
390 return self._get_sale_order(cr,uid,ids,field_name='name')
392 def _sale_ref_calc(self, cr, uid, ids, prop, unknow_none, unknow_dict):
393 return self._get_sale_order(cr,uid,ids,field_name='client_order_ref')
396 'name': fields.char('Reference', size=64, required=True),
397 'origin': fields.char('Origin', size=64),
398 'priority': fields.selection([('0','Not urgent'),('1','Normal'),('2','Urgent'),('3','Very Urgent')], 'Priority'),
400 'product_id': fields.many2one('product.product', 'Product', required=True, domain=[('type','<>','service')]),
401 'product_qty': fields.float('Product Qty', required=True, states={'draft':[('readonly',False)]}, readonly=True),
402 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True, states={'draft':[('readonly',False)]}, readonly=True),
403 'product_uos_qty': fields.float('Product UoS Qty', states={'draft':[('readonly',False)]}, readonly=True),
404 'product_uos': fields.many2one('product.uom', 'Product UoS', states={'draft':[('readonly',False)]}, readonly=True),
406 'location_src_id': fields.many2one('stock.location', 'Raw Materials Location', required=True,
407 help="Location where the system will look for products used in raw materials."),
408 'location_dest_id': fields.many2one('stock.location', 'Finished Products Location', required=True,
409 help="Location where the system will stock the finished products."),
411 'date_planned_date': fields.function(_production_date, method=True, type='date', string='Planned Date'),
412 'date_planned': fields.datetime('Scheduled date', required=True, select=1),
413 'date_start': fields.datetime('Start Date'),
414 'date_finnished': fields.datetime('End Date'),
416 'bom_id': fields.many2one('mrp.bom', 'Bill of Material', domain=[('bom_id','=',False)]),
417 'routing_id': fields.many2one('mrp.routing', string='Routing', on_delete='set null'),
419 'picking_id': fields.many2one('stock.picking', 'Packing list', readonly=True,
420 help="This is the internal picking list take bring the raw materials to the production plan."),
421 'move_prod_id': fields.many2one('stock.move', 'Move product', readonly=True),
422 'move_lines': fields.many2many('stock.move', 'mrp_production_move_ids', 'production_id', 'move_id', 'Products Consummed'),
424 'move_created_ids': fields.one2many('stock.move', 'production_id', 'Moves Created'),
425 'product_lines': fields.one2many('mrp.production.product.line', 'production_id', 'Scheduled goods'),
426 'workcenter_lines': fields.one2many('mrp.production.workcenter.line', 'production_id', 'Workcenters Utilisation'),
428 '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),
429 'hour_total': fields.function(_production_calc, method=True, type='float', string='Total Hours', multi='workorder'),
430 'cycle_total': fields.function(_production_calc, method=True, type='float', string='Total Cycles', multi='workorder'),
432 'sale_name': fields.function(_sale_name_calc, method=True, type='char', string='Sale Name'),
433 'sale_ref': fields.function(_sale_ref_calc, method=True, type='char', string='Sale Ref'),
436 'priority': lambda *a: '1',
437 'state': lambda *a: 'draft',
438 'date_planned': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
439 'product_qty': lambda *a: 1.0,
440 'name': lambda x,y,z,c: x.pool.get('ir.sequence').get(y,z,'mrp.production') or '/',
442 _order = 'date_planned asc, priority desc';
443 def unlink(self, cr, uid, ids, context=None):
444 productions = self.read(cr, uid, ids, ['state'])
446 for s in productions:
447 if s['state'] in ['draft','cancel']:
448 unlink_ids.append(s['id'])
450 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Production Order(s) which are in %s State!' % s['state']))
451 return osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
453 def copy(self, cr, uid, id, default=None,context=None):
457 'name': self.pool.get('ir.sequence').get(cr, uid, 'mrp.production'),
459 'move_created_ids': []
461 return super(mrp_production, self).copy(cr, uid, id, default, context)
463 def location_id_change(self, cr, uid, ids, src, dest, context={}):
467 return {'value': {'location_dest_id': src}}
470 def product_id_change(self, cr, uid, ids, product):
473 res = self.pool.get('product.product').read(cr, uid, [product], ['uom_id'])[0]
474 uom = res['uom_id'] and res['uom_id'][0]
475 result = {'product_uom':uom}
476 return {'value':result}
478 def action_picking_except(self, cr, uid, ids):
479 self.write(cr, uid, ids, {'state':'picking_except'})
482 def action_compute(self, cr, uid, ids, properties=[]):
484 for production in self.browse(cr, uid, ids):
485 cr.execute('delete from mrp_production_product_line where production_id=%s', (production.id,))
486 cr.execute('delete from mrp_production_workcenter_line where production_id=%s', (production.id,))
487 bom_point = production.bom_id
488 bom_id = production.bom_id.id
490 bom_id = self.pool.get('mrp.bom')._bom_find(cr, uid, production.product_id.id, production.product_uom.id, properties)
492 bom_point = self.pool.get('mrp.bom').browse(cr, uid, bom_id)
493 routing_id = bom_point.routing_id.id or False
494 self.write(cr, uid, [production.id], {'bom_id': bom_id, 'routing_id': routing_id})
497 raise osv.except_osv(_('Error'), _("Couldn't find bill of material for product"))
499 #if bom_point.routing_id and bom_point.routing_id.location_id:
500 # self.write(cr, uid, [production.id], {'location_src_id': bom_point.routing_id.location_id.id})
502 factor = production.product_qty * production.product_uom.factor / bom_point.product_uom.factor
503 res = self.pool.get('mrp.bom')._bom_explode(cr, uid, bom_point, factor / bom_point.product_qty, properties)
507 line['production_id'] = production.id
508 self.pool.get('mrp.production.product.line').create(cr, uid, line)
509 for line in results2:
510 line['production_id'] = production.id
511 self.pool.get('mrp.production.workcenter.line').create(cr, uid, line)
514 def action_cancel(self, cr, uid, ids):
515 for production in self.browse(cr, uid, ids):
516 if production.move_created_ids:
517 self.pool.get('stock.move').action_cancel(cr, uid, [x.id for x in production.move_created_ids])
518 self.pool.get('stock.move').action_cancel(cr, uid, [x.id for x in production.move_lines])
519 self.write(cr, uid, ids, {'state':'cancel'}) #,'move_lines':[(6,0,[])]})
522 #XXX: may be a bug here; lot_lines are unreserved for a few seconds;
523 # between the end of the picking list and the call to this function
524 def action_ready(self, cr, uid, ids):
525 self.write(cr, uid, ids, {'state':'ready'})
526 for production in self.browse(cr, uid, ids):
527 if production.move_prod_id:
528 self.pool.get('stock.move').write(cr, uid, [production.move_prod_id.id],
529 {'location_id':production.location_dest_id.id})
532 #TODO Review materials in function in_prod and prod_end.
533 def action_production_end(self, cr, uid, ids):
535 for production in self.browse(cr, uid, ids):
536 for res in production.move_lines:
537 for move in production.move_created_ids:
538 #XXX must use the orm
539 cr.execute('INSERT INTO stock_move_history_ids \
540 (parent_id, child_id) VALUES (%s,%s)',
542 move_ids.append(res.id)
543 vals= {'state':'confirmed'}
544 new_moves = [x.id for x in production.move_created_ids]
545 self.pool.get('stock.move').write(cr, uid, new_moves, vals)
546 if not production.date_finnished:
547 self.write(cr, uid, [production.id],
548 {'date_finnished': time.strftime('%Y-%m-%d %H:%M:%S')})
549 self.pool.get('stock.move').check_assign(cr, uid, new_moves)
550 self.pool.get('stock.move').action_done(cr, uid, new_moves)
551 self._costs_generate(cr, uid, production)
552 self.pool.get('stock.move').action_done(cr, uid, move_ids)
553 self.write(cr, uid, ids, {'state': 'done'})
556 def _costs_generate(self, cr, uid, production):
558 for wc_line in production.workcenter_lines:
559 wc = wc_line.workcenter_id
560 if wc.costs_journal_id and wc.costs_general_account_id:
561 value = wc_line.hour * wc.costs_hour
562 account = wc.costs_hour_account_id.id
563 if value and account:
565 self.pool.get('account.analytic.line').create(cr, uid, {
566 'name': wc_line.name+' (H)',
568 'account_id': account,
569 'general_account_id': wc.costs_general_account_id.id,
570 'journal_id': wc.costs_journal_id.id,
573 if wc.costs_journal_id and wc.costs_general_account_id:
574 value = wc_line.cycle * wc.costs_cycle
575 account = wc.costs_cycle_account_id.id
576 if value and account:
578 self.pool.get('account.analytic.line').create(cr, uid, {
579 'name': wc_line.name+' (C)',
581 'account_id': account,
582 'general_account_id': wc.costs_general_account_id.id,
583 'journal_id': wc.costs_journal_id.id,
588 def action_in_production(self, cr, uid, ids):
590 for production in self.browse(cr, uid, ids):
591 for res in production.move_lines:
592 move_ids.append(res.id)
593 if not production.date_start:
594 self.write(cr, uid, [production.id],
595 {'date_start': time.strftime('%Y-%m-%d %H:%M:%S')})
596 self.pool.get('stock.move').action_done(cr, uid, move_ids)
597 self.write(cr, uid, ids, {'state': 'in_production'})
600 def test_if_product(self, cr, uid, ids):
602 for production in self.browse(cr, uid, ids):
603 if not production.product_lines:
604 if not self.action_compute(cr, uid, [production.id]):
608 def _get_auto_picking(self, cr, uid, production):
611 def action_confirm(self, cr, uid, ids):
614 for production in self.browse(cr, uid, ids):
615 if not production.product_lines:
616 self.action_compute(cr, uid, [production.id])
617 production = self.browse(cr, uid, [production.id])[0]
619 pick_type = 'internal'
621 if production.bom_id.routing_id and production.bom_id.routing_id.location_id:
622 routing_loc = production.bom_id.routing_id.location_id
623 if routing_loc.usage<>'internal':
625 address_id = routing_loc.address_id and routing_loc.address_id.id or False
626 routing_loc = routing_loc.id
627 picking_id = self.pool.get('stock.picking').create(cr, uid, {
628 'origin': (production.origin or '').split(':')[0] +':'+production.name,
632 'address_id': address_id,
633 'auto_picking': self._get_auto_picking(cr, uid, production),
636 source = production.product_id.product_tmpl_id.property_stock_production.id
638 'name':'PROD:'+production.name,
639 'date_planned': production.date_planned,
640 'product_id': production.product_id.id,
641 'product_qty': production.product_qty,
642 'product_uom': production.product_uom.id,
643 'product_uos_qty': production.product_uos and production.product_uos_qty or False,
644 'product_uos': production.product_uos and production.product_uos.id or False,
645 'location_id': source,
646 'location_dest_id': production.location_dest_id.id,
647 'move_dest_id': production.move_prod_id.id,
650 res_final_id = self.pool.get('stock.move').create(cr, uid, data)
652 self.write(cr, uid, [production.id], {'move_created_ids': [(6, 0, [res_final_id])]})
654 for line in production.product_lines:
656 newdate = production.date_planned
657 if line.product_id.type in ('product', 'consu'):
658 res_dest_id = self.pool.get('stock.move').create(cr, uid, {
659 'name':'PROD:'+production.name,
660 'date_planned': production.date_planned,
661 'product_id': line.product_id.id,
662 'product_qty': line.product_qty,
663 'product_uom': line.product_uom.id,
664 'product_uos_qty': line.product_uos and line.product_uos_qty or False,
665 'product_uos': line.product_uos and line.product_uos.id or False,
666 'location_id': routing_loc or production.location_src_id.id,
667 'location_dest_id': source,
668 'move_dest_id': res_final_id,
671 moves.append(res_dest_id)
672 move_id = self.pool.get('stock.move').create(cr, uid, {
673 'name':'PROD:'+production.name,
674 'picking_id':picking_id,
675 'product_id': line.product_id.id,
676 'product_qty': line.product_qty,
677 'product_uom': line.product_uom.id,
678 'product_uos_qty': line.product_uos and line.product_uos_qty or False,
679 'product_uos': line.product_uos and line.product_uos.id or False,
680 'date_planned': newdate,
681 'move_dest_id': res_dest_id,
682 'location_id': production.location_src_id.id,
683 'location_dest_id': routing_loc or production.location_src_id.id,
686 proc_id = self.pool.get('mrp.procurement').create(cr, uid, {
687 'name': (production.origin or '').split(':')[0] + ':' + production.name,
688 'origin': (production.origin or '').split(':')[0] + ':' + production.name,
689 'date_planned': newdate,
690 'product_id': line.product_id.id,
691 'product_qty': line.product_qty,
692 'product_uom': line.product_uom.id,
693 'product_uos_qty': line.product_uos and line.product_qty or False,
694 'product_uos': line.product_uos and line.product_uos.id or False,
695 'location_id': production.location_src_id.id,
696 'procure_method': line.product_id.procure_method,
699 wf_service = netsvc.LocalService("workflow")
700 wf_service.trg_validate(uid, 'mrp.procurement', proc_id, 'button_confirm', cr)
701 proc_ids.append(proc_id)
702 wf_service = netsvc.LocalService("workflow")
703 wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
704 self.write(cr, uid, [production.id], {'picking_id':picking_id, 'move_lines': [(6,0,moves)], 'state':'confirmed'})
707 def force_production(self, cr, uid, ids, *args):
708 pick_obj = self.pool.get('stock.picking')
709 pick_obj.force_assign(cr, uid, [prod.picking_id.id for prod in self.browse(cr, uid, ids)])
715 class stock_move(osv.osv):
717 _inherit = 'stock.move'
719 'production_id': fields.many2one('mrp.production', 'Production', select=True),
723 class mrp_production_workcenter_line(osv.osv):
724 _name = 'mrp.production.workcenter.line'
725 _description = 'Production workcenters used'
727 'name': fields.char('Name', size=64, required=True),
728 'workcenter_id': fields.many2one('mrp.workcenter', 'Workcenter', required=True),
729 'cycle': fields.float('Nbr of cycle', digits=(16,2)),
730 'hour': fields.float('Nbr of hour', digits=(16,2)),
731 'sequence': fields.integer('Sequence', required=True),
732 'production_id': fields.many2one('mrp.production', 'Production Order', select=True),
735 'sequence': lambda *a: 1,
736 'hour': lambda *a: 0,
737 'cycle': lambda *a: 0,
739 mrp_production_workcenter_line()
741 class mrp_production_product_line(osv.osv):
742 _name = 'mrp.production.product.line'
743 _description = 'Production scheduled products'
745 'name': fields.char('Name', size=64, required=True),
746 'product_id': fields.many2one('product.product', 'Product', required=True),
747 'product_qty': fields.float('Product Qty', required=True),
748 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True),
749 'product_uos_qty': fields.float('Product UOS Qty'),
750 'product_uos': fields.many2one('product.uom', 'Product UOS'),
751 'production_id': fields.many2one('mrp.production', 'Production Order', select=True),
753 mrp_production_product_line()
755 # ------------------------------------------------------------------
757 # ------------------------------------------------------------------
759 # Produce, Buy or Find products and place a move
760 # then wizard for picking lists & move
762 class mrp_procurement(osv.osv):
763 _name = "mrp.procurement"
764 _description = "Procurement"
766 'name': fields.char('Name', size=64, required=True),
767 'origin': fields.char('Origin', size=64,
768 help="Reference of the document that created this procurement.\n"
769 "This is automatically completed by Open ERP."),
770 'priority': fields.selection([('0','Not urgent'),('1','Normal'),('2','Urgent'),('3','Very Urgent')], 'Priority', required=True),
771 'date_planned': fields.datetime('Scheduled date', required=True),
772 'date_close': fields.datetime('Date Closed'),
773 'product_id': fields.many2one('product.product', 'Product', required=True),
774 'product_qty': fields.float('Quantity', required=True, states={'draft':[('readonly',False)]}, readonly=True),
775 'product_uom': fields.many2one('product.uom', 'Product UoM', required=True, states={'draft':[('readonly',False)]}, readonly=True),
776 'product_uos_qty': fields.float('UoS Quantity', states={'draft':[('readonly',False)]}, readonly=True),
777 'product_uos': fields.many2one('product.uom', 'Product UoS', states={'draft':[('readonly',False)]}, readonly=True),
778 'move_id': fields.many2one('stock.move', 'Reservation', ondelete='set null'),
780 'bom_id': fields.many2one('mrp.bom', 'BoM', ondelete='cascade', select=True),
782 'close_move': fields.boolean('Close Move at end', required=True),
783 'location_id': fields.many2one('stock.location', 'Location', required=True),
784 'procure_method': fields.selection([('make_to_stock','from stock'),('make_to_order','on order')], 'Procurement Method', states={'draft':[('readonly',False)], 'confirmed':[('readonly',False)]},
785 readonly=True, required=True, help="If you encode manually a procurement, you probably want to use" \
786 " a make to order method."),
788 'purchase_id': fields.many2one('purchase.order', 'Purchase Order'),
789 'note': fields.text('Note'),
791 'property_ids': fields.many2many('mrp.property', 'mrp_procurement_property_rel', 'procurement_id','property_id', 'Properties'),
793 'message': fields.char('Latest error', size=64),
794 'state': fields.selection([
796 ('confirmed','Confirmed'),
797 ('exception','Exception'),
798 ('running','Running'),
802 ('waiting','Waiting')], 'Status', required=True),
803 'note' : fields.text('Note'),
806 'state': lambda *a: 'draft',
807 'priority': lambda *a: '1',
808 'date_planned': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
809 'close_move': lambda *a: 0,
810 'procure_method': lambda *a: 'make_to_order',
813 def unlink(self, cr, uid, ids, context=None):
814 procurements = self.read(cr, uid, ids, ['state'])
816 for s in procurements:
817 if s['state'] in ['draft','cancel']:
818 unlink_ids.append(s['id'])
820 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Procurement Order(s) which are in %s State!' % s['state']))
821 return osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
823 def onchange_product_id(self, cr, uid, ids, product_id, context={}):
825 w=self.pool.get('product.product').browse(cr,uid,product_id, context)
827 'product_uom':w.uom_id.id,
828 'product_uos':w.uos_id and w.uos_id.id or w.uom_id.id
833 def check_product(self, cr, uid, ids):
834 for procurement in self.browse(cr, uid, ids):
835 if procurement.product_id.type in ('product', 'consu'):
839 def check_move_cancel(self, cr, uid, ids, context={}):
842 for procurement in self.browse(cr, uid, ids, context):
843 if procurement.move_id:
845 if not procurement.move_id.state=='cancel':
849 def check_move_done(self, cr, uid, ids, context={}):
851 for proc in self.browse(cr, uid, ids, context):
853 if not proc.move_id.state=='done':
858 # This method may be overrided by objects that override mrp.procurment
859 # for computing their own purpose
861 def _quantity_compute_get(self, cr, uid, proc, context={}):
862 if proc.product_id.type=='product':
863 if proc.move_id.product_uos:
864 return proc.move_id.product_uos_qty
867 def _uom_compute_get(self, cr, uid, proc, context={}):
868 if proc.product_id.type=='product':
869 if proc.move_id.product_uos:
870 return proc.move_id.product_uos.id
874 # Return the quantity of product shipped/produced/served, wich may be
875 # different from the planned quantity
877 def quantity_get(self, cr, uid, id, context={}):
878 proc = self.browse(cr, uid, id, context)
879 result = self._quantity_compute_get(cr, uid, proc, context)
881 result = proc.product_qty
884 def uom_get(self, cr, uid, id, context=None):
885 proc = self.browse(cr, uid, id, context)
886 result = self._uom_compute_get(cr, uid, proc, context)
888 result = proc.product_uom.id
891 def check_waiting(self, cr, uid, ids, context=[]):
892 for procurement in self.browse(cr, uid, ids, context=context):
893 if procurement.move_id and procurement.move_id.state=='auto':
897 def check_produce_service(self, cr, uid, procurement, context=[]):
900 def check_produce_product(self, cr, uid, procurement, context=[]):
901 properties = [x.id for x in procurement.property_ids]
902 bom_id = self.pool.get('mrp.bom')._bom_find(cr, uid, procurement.product_id.id, procurement.product_uom.id, properties)
904 cr.execute('update mrp_procurement set message=%s where id=%s', (_('No BoM defined for this product !'), procurement.id))
908 def check_make_to_stock(self, cr, uid, ids, context={}):
910 for procurement in self.browse(cr, uid, ids, context=context):
911 if procurement.product_id.type=='service':
912 ok = ok and self._check_make_to_stock_service(cr, uid, procurement, context)
914 ok = ok and self._check_make_to_stock_product(cr, uid, procurement, context)
917 def check_produce(self, cr, uid, ids, context={}):
919 user = self.pool.get('res.users').browse(cr, uid, uid)
920 for procurement in self.browse(cr, uid, ids):
921 if procurement.product_id.product_tmpl_id.supply_method<>'produce':
922 if procurement.product_id.seller_ids:
923 partner = procurement.product_id.seller_ids[0].name
924 if user.company_id and user.company_id.partner_id:
925 if partner.id == user.company_id.partner_id.id:
928 if procurement.product_id.product_tmpl_id.type=='service':
929 res = res and self.check_produce_service(cr, uid, procurement, context)
931 res = res and self.check_produce_product(cr, uid, procurement, context)
936 def check_buy(self, cr, uid, ids):
937 user = self.pool.get('res.users').browse(cr, uid, uid)
938 for procurement in self.browse(cr, uid, ids):
939 if procurement.product_id.product_tmpl_id.supply_method<>'buy':
941 if not procurement.product_id.seller_ids:
942 cr.execute('update mrp_procurement set message=%s where id=%s', (_('No supplier defined for this product !'), procurement.id))
944 partner = procurement.product_id.seller_ids[0].name
945 if user.company_id and user.company_id.partner_id:
946 if partner.id == user.company_id.partner_id.id:
948 address_id = self.pool.get('res.partner').address_get(cr, uid, [partner.id], ['delivery'])['delivery']
950 cr.execute('update mrp_procurement set message=%s where id=%s', (_('No address defined for the supplier'), procurement.id))
954 def test_cancel(self, cr, uid, ids):
955 for record in self.browse(cr, uid, ids):
956 if record.move_id and record.move_id.state=='cancel':
960 def action_confirm(self, cr, uid, ids, context={}):
961 for procurement in self.browse(cr, uid, ids):
962 if procurement.product_id.type in ('product', 'consu'):
963 if not procurement.move_id:
964 source = procurement.location_id.id
965 if procurement.procure_method=='make_to_order':
966 source = procurement.product_id.product_tmpl_id.property_stock_procurement.id
967 id = self.pool.get('stock.move').create(cr, uid, {
968 'name': 'PROC:'+procurement.name,
969 'location_id': source,
970 'location_dest_id': procurement.location_id.id,
971 'product_id': procurement.product_id.id,
972 'product_qty':procurement.product_qty,
973 'product_uom': procurement.product_uom.id,
974 'date_planned': procurement.date_planned,
977 self.write(cr, uid, [procurement.id], {'move_id': id, 'close_move':1})
980 if procurement.procure_method=='make_to_stock' and procurement.move_id.state in ('waiting',):
981 id = self.pool.get('stock.move').write(cr, uid, [procurement.move_id.id], {'state':'confirmed'})
982 self.write(cr, uid, ids, {'state':'confirmed','message':''})
985 def action_move_assigned(self, cr, uid, ids):
986 self.write(cr, uid, ids, {'state':'running','message':_('from stock: products assigned.')})
989 def _check_make_to_stock_service(self, cr, uid, procurement, context={}):
992 def _check_make_to_stock_product(self, cr, uid, procurement, context={}):
994 if procurement.move_id:
995 id = procurement.move_id.id
996 if not (procurement.move_id.state in ('done','assigned','cancel')):
997 ok = ok and self.pool.get('stock.move').action_assign(cr, uid, [id])
998 cr.execute('select count(id) from stock_warehouse_orderpoint where product_id=%s', (procurement.product_id.id,))
999 if not cr.fetchone()[0]:
1000 cr.execute('update mrp_procurement set message=%s where id=%s', (_('from stock and no minimum orderpoint rule defined'), procurement.id))
1003 def action_produce_assign_service(self, cr, uid, ids, context={}):
1004 for procurement in self.browse(cr, uid, ids):
1005 self.write(cr, uid, [procurement.id], {'state':'running'})
1008 def action_produce_assign_product(self, cr, uid, ids, context={}):
1010 company = self.pool.get('res.users').browse(cr, uid, uid, context).company_id
1011 for procurement in self.browse(cr, uid, ids):
1012 res_id = procurement.move_id.id
1013 loc_id = procurement.location_id.id
1014 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)
1015 newdate = newdate - DateTime.RelativeDateTime(days=company.manufacturing_lead)
1016 produce_id = self.pool.get('mrp.production').create(cr, uid, {
1017 'origin': procurement.origin,
1018 'product_id': procurement.product_id.id,
1019 'product_qty': procurement.product_qty,
1020 'product_uom': procurement.product_uom.id,
1021 'product_uos_qty': procurement.product_uos and procurement.product_uos_qty or False,
1022 'product_uos': procurement.product_uos and procurement.product_uos.id or False,
1023 'location_src_id': procurement.location_id.id,
1024 'location_dest_id': procurement.location_id.id,
1025 'bom_id': procurement.bom_id and procurement.bom_id.id or False,
1026 'date_planned': newdate.strftime('%Y-%m-%d %H:%M:%S'),
1027 'move_prod_id': res_id,
1029 self.write(cr, uid, [procurement.id], {'state':'running'})
1030 bom_result = self.pool.get('mrp.production').action_compute(cr, uid,
1031 [produce_id], properties=[x.id for x in procurement.property_ids])
1032 wf_service = netsvc.LocalService("workflow")
1033 wf_service.trg_validate(uid, 'mrp.production', produce_id, 'button_confirm', cr)
1036 def action_po_assign(self, cr, uid, ids, context={}):
1038 company = self.pool.get('res.users').browse(cr, uid, uid, context).company_id
1039 for procurement in self.browse(cr, uid, ids):
1040 res_id = procurement.move_id.id
1041 partner = procurement.product_id.seller_ids[0].name
1042 partner_id = partner.id
1043 address_id = self.pool.get('res.partner').address_get(cr, uid, [partner_id], ['delivery'])['delivery']
1044 pricelist_id = partner.property_product_pricelist_purchase.id
1046 uom_id = procurement.product_id.uom_po_id.id
1048 qty = self.pool.get('product.uom')._compute_qty(cr, uid, procurement.product_uom.id, procurement.product_qty, uom_id)
1049 if procurement.product_id.seller_ids[0].qty:
1050 qty=max(qty,procurement.product_id.seller_ids[0].qty)
1052 price = self.pool.get('product.pricelist').price_get(cr, uid, [pricelist_id], procurement.product_id.id, qty, False, {'uom': uom_id})[pricelist_id]
1054 newdate = DateTime.strptime(procurement.date_planned, '%Y-%m-%d %H:%M:%S')
1055 newdate = newdate - DateTime.RelativeDateTime(days=company.po_lead)
1056 context.update({'lang':partner.lang})
1057 product=self.pool.get('product.product').browse(cr,uid,procurement.product_id.id,context=context)
1060 'name': product.name,
1062 'product_id': procurement.product_id.id,
1063 'product_uom': uom_id,
1064 'price_unit': price,
1065 'date_planned': newdate.strftime('%Y-%m-%d %H:%M:%S'),
1066 'move_dest_id': res_id,
1067 'notes':product.description_purchase,
1070 taxes_ids = procurement.product_id.product_tmpl_id.supplier_taxes_id
1071 taxes = self.pool.get('account.fiscal.position').map_tax(cr, uid, partner.property_account_position, taxes_ids)
1073 'taxes_id':[(6,0,taxes)]
1075 purchase_id = self.pool.get('purchase.order').create(cr, uid, {
1076 'origin': procurement.origin,
1077 'partner_id': partner_id,
1078 'partner_address_id': address_id,
1079 'location_id': procurement.location_id.id,
1080 'pricelist_id': pricelist_id,
1081 'order_line': [(0,0,line)],
1082 'fiscal_position': partner.property_account_position and partner.property_account_position.id or False
1084 self.write(cr, uid, [procurement.id], {'state':'running', 'purchase_id':purchase_id})
1087 def action_cancel(self, cr, uid, ids):
1090 for proc in self.browse(cr, uid, ids):
1092 if proc.move_id.state not in ('done','cancel'):
1093 todo2.append(proc.move_id.id)
1095 if proc.move_id and proc.move_id.state=='waiting':
1096 todo.append(proc.move_id.id)
1098 self.pool.get('stock.move').action_cancel(cr, uid, todo2)
1100 self.pool.get('stock.move').write(cr, uid, todo, {'state':'assigned'})
1101 self.write(cr, uid, ids, {'state':'cancel'})
1102 wf_service = netsvc.LocalService("workflow")
1104 wf_service.trg_trigger(uid, 'mrp.procurement', id, cr)
1107 def action_check_finnished(self, cr, uid, ids):
1108 return self.check_move_done(cr, uid, ids)
1110 def action_check(self, cr, uid, ids):
1112 for procurement in self.browse(cr, uid, ids):
1113 if procurement.move_id.state=='assigned' or procurement.move_id.state=='done':
1114 self.action_done(cr, uid, [procurement.id])
1118 def action_ready(self, cr, uid, ids):
1119 res = self.write(cr, uid, ids, {'state':'ready'})
1122 def action_done(self, cr, uid, ids):
1123 for procurement in self.browse(cr, uid, ids):
1124 if procurement.move_id:
1125 if procurement.close_move and (procurement.move_id.state <> 'done'):
1126 self.pool.get('stock.move').action_done(cr, uid, [procurement.move_id.id])
1127 res = self.write(cr, uid, ids, {'state':'done', 'date_close':time.strftime('%Y-%m-%d')})
1128 wf_service = netsvc.LocalService("workflow")
1130 wf_service.trg_trigger(uid, 'mrp.procurement', id, cr)
1133 def run_scheduler(self, cr, uid, automatic=False, use_new_cursor=False, context=None):
1135 use_new_cursor: False or the dbname
1139 self._procure_confirm(cr, uid, use_new_cursor=use_new_cursor, context=context)
1140 self._procure_orderpoint_confirm(cr, uid, automatic=automatic,\
1141 use_new_cursor=use_new_cursor, context=context)
1145 class stock_warehouse_orderpoint(osv.osv):
1146 _name = "stock.warehouse.orderpoint"
1147 _description = "Orderpoint minimum rule"
1149 'name': fields.char('Name', size=32, required=True),
1150 'active': fields.boolean('Active'),
1151 'logic': fields.selection([('max','Order to Max'),('price','Best price (not yet active!)')], 'Reordering Mode', required=True),
1152 'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse', required=True),
1153 'location_id': fields.many2one('stock.location', 'Location', required=True),
1154 'product_id': fields.many2one('product.product', 'Product', required=True, domain=[('type','=','product')]),
1155 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True ),
1156 'product_min_qty': fields.float('Min Quantity', required=True,
1157 help="When the virtual stock goes belong the Min Quantity, Open ERP generates "\
1158 "a procurement to bring the virtual stock to the Max Quantity."),
1159 'product_max_qty': fields.float('Max Quantity', required=True,
1160 help="When the virtual stock goes belong the Min Quantity, Open ERP generates "\
1161 "a procurement to bring the virtual stock to the Max Quantity."),
1162 'qty_multiple': fields.integer('Qty Multiple', required=True,
1163 help="The procurement quantity will by rounded up to this multiple."),
1164 'procurement_id': fields.many2one('mrp.procurement', 'Purchase Order')
1167 'active': lambda *a: 1,
1168 'logic': lambda *a: 'max',
1169 'qty_multiple': lambda *a: 1,
1170 'name': lambda x,y,z,c: x.pool.get('ir.sequence').get(y,z,'mrp.warehouse.orderpoint') or '',
1171 'product_uom': lambda sel, cr, uid, context: context.get('product_uom', False),
1173 def onchange_warehouse_id(self, cr, uid, ids, warehouse_id, context={}):
1175 w=self.pool.get('stock.warehouse').browse(cr,uid,warehouse_id, context)
1176 v = {'location_id':w.lot_stock_id.id}
1179 def onchange_product_id(self, cr, uid, ids, product_id, context={}):
1181 prod=self.pool.get('product.product').browse(cr,uid,product_id)
1182 v = {'product_uom':prod.uom_id.id}
1185 def copy(self, cr, uid, id, default=None,context={}):
1189 'name': self.pool.get('ir.sequence').get(cr, uid, 'mrp.warehouse.orderpoint') or '',
1191 return super(stock_warehouse_orderpoint, self).copy(cr, uid, id, default, context)
1192 stock_warehouse_orderpoint()
1195 class StockMove(osv.osv):
1196 _inherit = 'stock.move'
1198 'procurements': fields.one2many('mrp.procurement', 'move_id', 'Procurements'),
1200 def copy(self, cr, uid, id, default=None, context=None):
1201 default = default or {}
1202 default['procurements'] = []
1203 return super(StockMove, self).copy(cr, uid, id, default, context)
1205 def _action_explode(self, cr, uid, move, context={}):
1206 if move.product_id.supply_method=='produce' and move.product_id.procure_method=='make_to_order':
1207 bis = self.pool.get('mrp.bom').search(cr, uid, [
1208 ('product_id','=',move.product_id.id),
1209 ('bom_id','=',False),
1210 ('type','=','phantom')])
1212 factor = move.product_qty
1213 bom_point = self.pool.get('mrp.bom').browse(cr, uid, bis[0])
1214 res = self.pool.get('mrp.bom')._bom_explode(cr, uid, bom_point, factor, [])
1215 dest = move.product_id.product_tmpl_id.property_stock_production.id
1217 if move.state=='assigned':
1221 'picking_id': move.picking_id.id,
1222 'product_id': line['product_id'],
1223 'product_uom': line['product_uom'],
1224 'product_qty': line['product_qty'],
1225 'product_uos': line['product_uos'],
1226 'product_uos_qty': line['product_uos_qty'],
1227 'move_dest_id': move.id,
1229 'name': line['name'],
1230 'location_dest_id': dest,
1231 'move_history_ids': [(6,0,[move.id])],
1232 'move_history_ids2': [(6,0,[])],
1235 mid = self.pool.get('stock.move').copy(cr, uid, move.id, default=valdef)
1236 prodobj = self.pool.get('product.product').browse(cr, uid, line['product_id'], context=context)
1237 proc_id = self.pool.get('mrp.procurement').create(cr, uid, {
1238 'name': (move.picking_id.origin or ''),
1239 'origin': (move.picking_id.origin or ''),
1240 'date_planned': move.date_planned,
1241 'product_id': line['product_id'],
1242 'product_qty': line['product_qty'],
1243 'product_uom': line['product_uom'],
1244 'product_uos_qty': line['product_uos'] and line['product_uos_qty'] or False,
1245 'product_uos': line['product_uos'],
1246 'location_id': move.location_id.id,
1247 'procure_method': prodobj.procure_method,
1250 wf_service = netsvc.LocalService("workflow")
1251 wf_service.trg_validate(uid, 'mrp.procurement', proc_id, 'button_confirm', cr)
1252 self.pool.get('stock.move').write(cr, uid, [move.id], {
1253 'location_id': move.location_dest_id.id,
1254 'auto_validate': True,
1255 'picking_id': False,
1256 'location_id': dest,
1259 for m in self.pool.get('mrp.procurement').search(cr, uid, [('move_id','=',move.id)], context):
1260 wf_service = netsvc.LocalService("workflow")
1261 wf_service.trg_validate(uid, 'mrp.procurement', m, 'button_wait_done', cr)
1266 class StockPicking(osv.osv):
1267 _inherit = 'stock.picking'
1269 def test_finnished(self, cursor, user, ids):
1270 wf_service = netsvc.LocalService("workflow")
1271 res = super(StockPicking, self).test_finnished(cursor, user, ids)
1272 for picking in self.browse(cursor, user, ids):
1273 for move in picking.move_lines:
1274 if move.state == 'done' and move.procurements:
1275 for procurement in move.procurements:
1276 wf_service.trg_validate(user, 'mrp.procurement',
1277 procurement.id, 'button_check', cursor)
1281 # Explode picking by replacing phantom BoMs
1283 def action_explode(self, cr, uid, picks, *args):
1284 for move in self.pool.get('stock.move').browse(cr, uid, picks):
1285 self.pool.get('stock.move')._action_explode(cr, uid, move)
1290 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: