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, ondelete='cascade'),
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 #TODO: fix me sale module can not be used here,
366 #as may be mrp can be installed without sale module
367 if field_name=='name':
368 res[production['id']]=move.sale_line_id and move.sale_line_id.order_id.name or False
369 if field_name=='client_order_ref':
370 res[production['id']]=move.sale_line_id and move.sale_line_id.order_id.client_order_ref or False
373 def _production_calc(self, cr, uid, ids, prop, unknow_none, context={}):
375 for prod in self.browse(cr, uid, ids, context=context):
380 for wc in prod.workcenter_lines:
381 result[prod.id]['hour_total'] += wc.hour
382 result[prod.id]['cycle_total'] += wc.cycle
385 def _production_date_end(self, cr, uid, ids, prop, unknow_none, context={}):
387 for prod in self.browse(cr, uid, ids, context=context):
388 result[prod.id] = prod.date_planned
391 def _production_date(self, cr, uid, ids, prop, unknow_none, context={}):
393 for prod in self.browse(cr, uid, ids, context=context):
394 result[prod.id] = prod.date_planned[:10]
397 def _sale_name_calc(self, cr, uid, ids, prop, unknow_none, unknow_dict):
398 return self._get_sale_order(cr,uid,ids,field_name='name')
400 def _sale_ref_calc(self, cr, uid, ids, prop, unknow_none, unknow_dict):
401 return self._get_sale_order(cr,uid,ids,field_name='client_order_ref')
404 'name': fields.char('Reference', size=64, required=True),
405 'origin': fields.char('Origin', size=64),
406 'priority': fields.selection([('0','Not urgent'),('1','Normal'),('2','Urgent'),('3','Very Urgent')], 'Priority'),
408 'product_id': fields.many2one('product.product', 'Product', required=True, domain=[('type','<>','service')]),
409 'product_qty': fields.float('Product Qty', required=True, states={'draft':[('readonly',False)]}, readonly=True),
410 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True, states={'draft':[('readonly',False)]}, readonly=True),
411 'product_uos_qty': fields.float('Product UoS Qty', states={'draft':[('readonly',False)]}, readonly=True),
412 'product_uos': fields.many2one('product.uom', 'Product UoS', states={'draft':[('readonly',False)]}, readonly=True),
414 'location_src_id': fields.many2one('stock.location', 'Raw Materials Location', required=True,
415 help="Location where the system will look for products used in raw materials."),
416 'location_dest_id': fields.many2one('stock.location', 'Finished Products Location', required=True,
417 help="Location where the system will stock the finished products."),
419 'date_planned_end': fields.function(_production_date_end, method=True, type='date', string='Scheduled End'),
420 'date_planned_date': fields.function(_production_date, method=True, type='date', string='Scheduled Date'),
421 'date_planned': fields.datetime('Scheduled date', required=True, select=1),
422 'date_start': fields.datetime('Start Date'),
423 'date_finnished': fields.datetime('End Date'),
425 'bom_id': fields.many2one('mrp.bom', 'Bill of Material', domain=[('bom_id','=',False)]),
426 'routing_id': fields.many2one('mrp.routing', string='Routing', on_delete='set null'),
428 'picking_id': fields.many2one('stock.picking', 'Packing list', readonly=True,
429 help="This is the internal picking list take bring the raw materials to the production plan."),
430 'move_prod_id': fields.many2one('stock.move', 'Move product', readonly=True),
431 'move_lines': fields.many2many('stock.move', 'mrp_production_move_ids', 'production_id', 'move_id', 'Products Consummed'),
433 'move_created_ids': fields.one2many('stock.move', 'production_id', 'Moves Created'),
434 'product_lines': fields.one2many('mrp.production.product.line', 'production_id', 'Scheduled goods'),
435 'workcenter_lines': fields.one2many('mrp.production.workcenter.line', 'production_id', 'Workcenters Utilisation'),
437 '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),
438 'hour_total': fields.function(_production_calc, method=True, type='float', string='Total Hours', multi='workorder'),
439 'cycle_total': fields.function(_production_calc, method=True, type='float', string='Total Cycles', multi='workorder'),
441 'sale_name': fields.function(_sale_name_calc, method=True, type='char', string='Sale Name'),
442 'sale_ref': fields.function(_sale_ref_calc, method=True, type='char', string='Sale Ref'),
445 'priority': lambda *a: '1',
446 'state': lambda *a: 'draft',
447 'date_planned': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
448 'product_qty': lambda *a: 1.0,
449 'name': lambda x,y,z,c: x.pool.get('ir.sequence').get(y,z,'mrp.production') or '/',
451 _order = 'date_planned asc, priority desc';
452 def unlink(self, cr, uid, ids, context=None):
453 productions = self.read(cr, uid, ids, ['state'])
455 for s in productions:
456 if s['state'] in ['draft','cancel']:
457 unlink_ids.append(s['id'])
459 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Production Order(s) which are in %s State!' % s['state']))
460 return osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
462 def copy(self, cr, uid, id, default=None,context=None):
466 'name': self.pool.get('ir.sequence').get(cr, uid, 'mrp.production'),
468 'move_created_ids': [],
471 return super(mrp_production, self).copy(cr, uid, id, default, context)
473 def location_id_change(self, cr, uid, ids, src, dest, context={}):
477 return {'value': {'location_dest_id': src}}
480 def product_id_change(self, cr, uid, ids, product):
483 res = self.pool.get('product.product').read(cr, uid, [product], ['uom_id'])[0]
484 uom = res['uom_id'] and res['uom_id'][0]
485 result = {'product_uom':uom}
486 return {'value':result}
488 def bom_id_change(self, cr, uid, ids, product):
491 res = self.pool.get('mrp.bom').read(cr, uid, [product], ['routing_id'])[0]
492 routing_id = res['routing_id'] and res['routing_id'][0]
493 result = {'routing_id':routing_id}
494 return {'value':result}
496 def action_picking_except(self, cr, uid, ids):
497 self.write(cr, uid, ids, {'state':'picking_except'})
500 def action_compute(self, cr, uid, ids, properties=[]):
502 for production in self.browse(cr, uid, ids):
503 cr.execute('delete from mrp_production_product_line where production_id=%s', (production.id,))
504 cr.execute('delete from mrp_production_workcenter_line where production_id=%s', (production.id,))
505 bom_point = production.bom_id
506 bom_id = production.bom_id.id
508 bom_id = self.pool.get('mrp.bom')._bom_find(cr, uid, production.product_id.id, production.product_uom.id, properties)
510 bom_point = self.pool.get('mrp.bom').browse(cr, uid, bom_id)
511 routing_id = bom_point.routing_id.id or False
512 self.write(cr, uid, [production.id], {'bom_id': bom_id, 'routing_id': routing_id})
515 raise osv.except_osv(_('Error'), _("Couldn't find bill of material for product"))
517 #if bom_point.routing_id and bom_point.routing_id.location_id:
518 # self.write(cr, uid, [production.id], {'location_src_id': bom_point.routing_id.location_id.id})
520 factor = production.product_qty * production.product_uom.factor / bom_point.product_uom.factor
521 res = self.pool.get('mrp.bom')._bom_explode(cr, uid, bom_point, factor / bom_point.product_qty, properties)
525 line['production_id'] = production.id
526 self.pool.get('mrp.production.product.line').create(cr, uid, line)
527 for line in results2:
528 line['production_id'] = production.id
529 self.pool.get('mrp.production.workcenter.line').create(cr, uid, line)
532 def action_cancel(self, cr, uid, ids):
533 for production in self.browse(cr, uid, ids):
534 if production.move_created_ids:
535 self.pool.get('stock.move').action_cancel(cr, uid, [x.id for x in production.move_created_ids])
536 self.pool.get('stock.move').action_cancel(cr, uid, [x.id for x in production.move_lines])
537 self.write(cr, uid, ids, {'state':'cancel'}) #,'move_lines':[(6,0,[])]})
540 #XXX: may be a bug here; lot_lines are unreserved for a few seconds;
541 # between the end of the picking list and the call to this function
542 def action_ready(self, cr, uid, ids):
543 self.write(cr, uid, ids, {'state':'ready'})
544 for production in self.browse(cr, uid, ids):
545 if production.move_prod_id:
546 self.pool.get('stock.move').write(cr, uid, [production.move_prod_id.id],
547 {'location_id':production.location_dest_id.id})
550 #TODO Review materials in function in_prod and prod_end.
551 def action_production_end(self, cr, uid, ids):
553 for production in self.browse(cr, uid, ids):
554 for res in production.move_lines:
555 for move in production.move_created_ids:
556 #XXX must use the orm
557 cr.execute('INSERT INTO stock_move_history_ids \
558 (parent_id, child_id) VALUES (%s,%s)',
560 # move_ids.append(res.id)
561 vals= {'state':'confirmed'}
562 new_moves = [x.id for x in production.move_created_ids]
563 self.pool.get('stock.move').write(cr, uid, new_moves, vals)
564 if not production.date_finnished:
565 self.write(cr, uid, [production.id],
566 {'date_finnished': time.strftime('%Y-%m-%d %H:%M:%S')})
567 self.pool.get('stock.move').check_assign(cr, uid, new_moves)
568 self.pool.get('stock.move').action_done(cr, uid, new_moves)
569 self._costs_generate(cr, uid, production)
570 # self.pool.get('stock.move').action_done(cr, uid, move_ids)
571 self.write(cr, uid, ids, {'state': 'done'})
574 def _costs_generate(self, cr, uid, production):
576 for wc_line in production.workcenter_lines:
577 wc = wc_line.workcenter_id
578 if wc.costs_journal_id and wc.costs_general_account_id:
579 value = wc_line.hour * wc.costs_hour
580 account = wc.costs_hour_account_id.id
581 if value and account:
583 self.pool.get('account.analytic.line').create(cr, uid, {
584 'name': wc_line.name+' (H)',
586 'account_id': account,
587 'general_account_id': wc.costs_general_account_id.id,
588 'journal_id': wc.costs_journal_id.id,
591 if wc.costs_journal_id and wc.costs_general_account_id:
592 value = wc_line.cycle * wc.costs_cycle
593 account = wc.costs_cycle_account_id.id
594 if value and account:
596 self.pool.get('account.analytic.line').create(cr, uid, {
597 'name': wc_line.name+' (C)',
599 'account_id': account,
600 'general_account_id': wc.costs_general_account_id.id,
601 'journal_id': wc.costs_journal_id.id,
606 def action_in_production(self, cr, uid, ids):
608 for production in self.browse(cr, uid, ids):
609 for res in production.move_lines:
610 move_ids.append(res.id)
611 if not production.date_start:
612 self.write(cr, uid, [production.id],
613 {'date_start': time.strftime('%Y-%m-%d %H:%M:%S')})
614 self.pool.get('stock.move').action_done(cr, uid, move_ids)
615 self.write(cr, uid, ids, {'state': 'in_production'})
618 def test_if_product(self, cr, uid, ids):
620 for production in self.browse(cr, uid, ids):
621 if not production.product_lines:
622 if not self.action_compute(cr, uid, [production.id]):
626 def _get_auto_picking(self, cr, uid, production):
629 def action_confirm(self, cr, uid, ids):
632 for production in self.browse(cr, uid, ids):
633 if not production.product_lines:
634 self.action_compute(cr, uid, [production.id])
635 production = self.browse(cr, uid, [production.id])[0]
637 pick_type = 'internal'
639 if production.bom_id.routing_id and production.bom_id.routing_id.location_id:
640 routing_loc = production.bom_id.routing_id.location_id
641 if routing_loc.usage<>'internal':
643 address_id = routing_loc.address_id and routing_loc.address_id.id or False
644 routing_loc = routing_loc.id
645 picking_id = self.pool.get('stock.picking').create(cr, uid, {
646 'origin': (production.origin or '').split(':')[0] +':'+production.name,
650 'address_id': address_id,
651 'auto_picking': self._get_auto_picking(cr, uid, production),
654 source = production.product_id.product_tmpl_id.property_stock_production.id
656 'name':'PROD:'+production.name,
657 'date_planned': production.date_planned,
658 'product_id': production.product_id.id,
659 'product_qty': production.product_qty,
660 'product_uom': production.product_uom.id,
661 'product_uos_qty': production.product_uos and production.product_uos_qty or False,
662 'product_uos': production.product_uos and production.product_uos.id or False,
663 'location_id': source,
664 'location_dest_id': production.location_dest_id.id,
665 'move_dest_id': production.move_prod_id.id,
668 res_final_id = self.pool.get('stock.move').create(cr, uid, data)
670 self.write(cr, uid, [production.id], {'move_created_ids': [(6, 0, [res_final_id])]})
672 for line in production.product_lines:
674 newdate = production.date_planned
675 if line.product_id.type in ('product', 'consu'):
676 res_dest_id = self.pool.get('stock.move').create(cr, uid, {
677 'name':'PROD:'+production.name,
678 'date_planned': production.date_planned,
679 'product_id': line.product_id.id,
680 'product_qty': line.product_qty,
681 'product_uom': line.product_uom.id,
682 'product_uos_qty': line.product_uos and line.product_uos_qty or False,
683 'product_uos': line.product_uos and line.product_uos.id or False,
684 'location_id': routing_loc or production.location_src_id.id,
685 'location_dest_id': source,
686 'move_dest_id': res_final_id,
689 moves.append(res_dest_id)
690 move_id = self.pool.get('stock.move').create(cr, uid, {
691 'name':'PROD:'+production.name,
692 'picking_id':picking_id,
693 'product_id': line.product_id.id,
694 'product_qty': line.product_qty,
695 'product_uom': line.product_uom.id,
696 'product_uos_qty': line.product_uos and line.product_uos_qty or False,
697 'product_uos': line.product_uos and line.product_uos.id or False,
698 'date_planned': newdate,
699 'move_dest_id': res_dest_id,
700 'location_id': production.location_src_id.id,
701 'location_dest_id': routing_loc or production.location_src_id.id,
704 proc_id = self.pool.get('mrp.procurement').create(cr, uid, {
705 'name': (production.origin or '').split(':')[0] + ':' + production.name,
706 'origin': (production.origin or '').split(':')[0] + ':' + production.name,
707 'date_planned': newdate,
708 'product_id': line.product_id.id,
709 'product_qty': line.product_qty,
710 'product_uom': line.product_uom.id,
711 'product_uos_qty': line.product_uos and line.product_qty or False,
712 'product_uos': line.product_uos and line.product_uos.id or False,
713 'location_id': production.location_src_id.id,
714 'procure_method': line.product_id.procure_method,
717 wf_service = netsvc.LocalService("workflow")
718 wf_service.trg_validate(uid, 'mrp.procurement', proc_id, 'button_confirm', cr)
719 proc_ids.append(proc_id)
720 wf_service = netsvc.LocalService("workflow")
721 wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
722 self.write(cr, uid, [production.id], {'picking_id':picking_id, 'move_lines': [(6,0,moves)], 'state':'confirmed'})
725 def force_production(self, cr, uid, ids, *args):
726 pick_obj = self.pool.get('stock.picking')
727 pick_obj.force_assign(cr, uid, [prod.picking_id.id for prod in self.browse(cr, uid, ids)])
733 class stock_move(osv.osv):
735 _inherit = 'stock.move'
737 'production_id': fields.many2one('mrp.production', 'Production', select=True),
741 class mrp_production_workcenter_line(osv.osv):
742 _name = 'mrp.production.workcenter.line'
743 _description = 'Work Orders'
746 'name': fields.char('Work Order', size=64, required=True),
747 'workcenter_id': fields.many2one('mrp.workcenter', 'Workcenter', required=True),
748 'cycle': fields.float('Nbr of cycle', digits=(16,2)),
749 'hour': fields.float('Nbr of hour', digits=(16,2)),
750 'sequence': fields.integer('Sequence', required=True),
751 'production_id': fields.many2one('mrp.production', 'Production Order', select=True, ondelete='cascade'),
754 'sequence': lambda *a: 1,
755 'hour': lambda *a: 0,
756 'cycle': lambda *a: 0,
758 mrp_production_workcenter_line()
760 class mrp_production_product_line(osv.osv):
761 _name = 'mrp.production.product.line'
762 _description = 'Production scheduled products'
764 'name': fields.char('Name', size=64, required=True),
765 'product_id': fields.many2one('product.product', 'Product', required=True),
766 'product_qty': fields.float('Product Qty', required=True),
767 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True),
768 'product_uos_qty': fields.float('Product UOS Qty'),
769 'product_uos': fields.many2one('product.uom', 'Product UOS'),
770 'production_id': fields.many2one('mrp.production', 'Production Order', select=True),
772 mrp_production_product_line()
774 # ------------------------------------------------------------------
776 # ------------------------------------------------------------------
778 # Produce, Buy or Find products and place a move
779 # then wizard for picking lists & move
781 class mrp_procurement(osv.osv):
782 _name = "mrp.procurement"
783 _description = "Procurement"
785 'name': fields.char('Name', size=64, required=True),
786 'origin': fields.char('Origin', size=64,
787 help="Reference of the document that created this procurement.\n"
788 "This is automatically completed by Open ERP."),
789 'priority': fields.selection([('0','Not urgent'),('1','Normal'),('2','Urgent'),('3','Very Urgent')], 'Priority', required=True),
790 'date_planned': fields.datetime('Scheduled date', required=True),
791 'date_close': fields.datetime('Date Closed'),
792 'product_id': fields.many2one('product.product', 'Product', required=True),
793 'product_qty': fields.float('Quantity', required=True, states={'draft':[('readonly',False)]}, readonly=True),
794 'product_uom': fields.many2one('product.uom', 'Product UoM', required=True, states={'draft':[('readonly',False)]}, readonly=True),
795 'product_uos_qty': fields.float('UoS Quantity', states={'draft':[('readonly',False)]}, readonly=True),
796 'product_uos': fields.many2one('product.uom', 'Product UoS', states={'draft':[('readonly',False)]}, readonly=True),
797 'move_id': fields.many2one('stock.move', 'Reservation', ondelete='set null'),
799 'bom_id': fields.many2one('mrp.bom', 'BoM', ondelete='cascade', select=True),
801 'close_move': fields.boolean('Close Move at end', required=True),
802 'location_id': fields.many2one('stock.location', 'Location', required=True),
803 'procure_method': fields.selection([('make_to_stock','from stock'),('make_to_order','on order')], 'Procurement Method', states={'draft':[('readonly',False)], 'confirmed':[('readonly',False)]},
804 readonly=True, required=True, help="If you encode manually a procurement, you probably want to use" \
805 " a make to order method."),
807 'purchase_id': fields.many2one('purchase.order', 'Purchase Order'),
808 'note': fields.text('Note'),
810 'property_ids': fields.many2many('mrp.property', 'mrp_procurement_property_rel', 'procurement_id','property_id', 'Properties'),
812 'message': fields.char('Latest error', size=64),
813 'state': fields.selection([
815 ('confirmed','Confirmed'),
816 ('exception','Exception'),
817 ('running','Running'),
821 ('waiting','Waiting')], 'Status', required=True),
822 'note' : fields.text('Note'),
825 'state': lambda *a: 'draft',
826 'priority': lambda *a: '1',
827 'date_planned': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
828 'close_move': lambda *a: 0,
829 'procure_method': lambda *a: 'make_to_order',
832 def unlink(self, cr, uid, ids, context=None):
833 procurements = self.read(cr, uid, ids, ['state'])
835 for s in procurements:
836 if s['state'] in ['draft','cancel']:
837 unlink_ids.append(s['id'])
839 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Procurement Order(s) which are in %s State!' % s['state']))
840 return osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
842 def onchange_product_id(self, cr, uid, ids, product_id, context={}):
844 w=self.pool.get('product.product').browse(cr,uid,product_id, context)
846 'product_uom':w.uom_id.id,
847 'product_uos':w.uos_id and w.uos_id.id or w.uom_id.id
852 def check_product(self, cr, uid, ids):
853 for procurement in self.browse(cr, uid, ids):
854 if procurement.product_id.type in ('product', 'consu'):
858 def check_move_cancel(self, cr, uid, ids, context={}):
861 for procurement in self.browse(cr, uid, ids, context):
862 if procurement.move_id:
864 if not procurement.move_id.state=='cancel':
868 def check_move_done(self, cr, uid, ids, context={}):
870 for proc in self.browse(cr, uid, ids, context):
872 if not proc.move_id.state=='done':
877 # This method may be overrided by objects that override mrp.procurment
878 # for computing their own purpose
880 def _quantity_compute_get(self, cr, uid, proc, context={}):
881 if proc.product_id.type=='product':
882 if proc.move_id.product_uos:
883 return proc.move_id.product_uos_qty
886 def _uom_compute_get(self, cr, uid, proc, context={}):
887 if proc.product_id.type=='product':
888 if proc.move_id.product_uos:
889 return proc.move_id.product_uos.id
893 # Return the quantity of product shipped/produced/served, wich may be
894 # different from the planned quantity
896 def quantity_get(self, cr, uid, id, context={}):
897 proc = self.browse(cr, uid, id, context)
898 result = self._quantity_compute_get(cr, uid, proc, context)
900 result = proc.product_qty
903 def uom_get(self, cr, uid, id, context=None):
904 proc = self.browse(cr, uid, id, context)
905 result = self._uom_compute_get(cr, uid, proc, context)
907 result = proc.product_uom.id
910 def check_waiting(self, cr, uid, ids, context=[]):
911 for procurement in self.browse(cr, uid, ids, context=context):
912 if procurement.move_id and procurement.move_id.state=='auto':
916 def check_produce_service(self, cr, uid, procurement, context=[]):
919 def check_produce_product(self, cr, uid, procurement, context=[]):
920 properties = [x.id for x in procurement.property_ids]
921 bom_id = self.pool.get('mrp.bom')._bom_find(cr, uid, procurement.product_id.id, procurement.product_uom.id, properties)
923 cr.execute('update mrp_procurement set message=%s where id=%s', (_('No BoM defined for this product !'), procurement.id))
927 def check_make_to_stock(self, cr, uid, ids, context={}):
929 for procurement in self.browse(cr, uid, ids, context=context):
930 if procurement.product_id.type=='service':
931 ok = ok and self._check_make_to_stock_service(cr, uid, procurement, context)
933 ok = ok and self._check_make_to_stock_product(cr, uid, procurement, context)
936 def check_produce(self, cr, uid, ids, context={}):
938 user = self.pool.get('res.users').browse(cr, uid, uid)
939 for procurement in self.browse(cr, uid, ids):
940 if procurement.product_id.product_tmpl_id.supply_method<>'produce':
941 if procurement.product_id.seller_ids:
942 partner = procurement.product_id.seller_ids[0].name
943 if user.company_id and user.company_id.partner_id:
944 if partner.id == user.company_id.partner_id.id:
947 if procurement.product_id.product_tmpl_id.type=='service':
948 res = res and self.check_produce_service(cr, uid, procurement, context)
950 res = res and self.check_produce_product(cr, uid, procurement, context)
955 def check_buy(self, cr, uid, ids):
956 user = self.pool.get('res.users').browse(cr, uid, uid)
957 for procurement in self.browse(cr, uid, ids):
958 if procurement.product_id.product_tmpl_id.supply_method<>'buy':
960 if not procurement.product_id.seller_ids:
961 cr.execute('update mrp_procurement set message=%s where id=%s', (_('No supplier defined for this product !'), procurement.id))
963 partner = procurement.product_id.seller_ids[0].name
964 if user.company_id and user.company_id.partner_id:
965 if partner.id == user.company_id.partner_id.id:
967 address_id = self.pool.get('res.partner').address_get(cr, uid, [partner.id], ['delivery'])['delivery']
969 cr.execute('update mrp_procurement set message=%s where id=%s', (_('No address defined for the supplier'), procurement.id))
973 def test_cancel(self, cr, uid, ids):
974 for record in self.browse(cr, uid, ids):
975 if record.move_id and record.move_id.state=='cancel':
979 def action_confirm(self, cr, uid, ids, context={}):
980 for procurement in self.browse(cr, uid, ids):
981 if procurement.product_qty <= 0.00:
982 raise osv.except_osv(_('Data Insufficient !'), _('Please check the Quantity of Procurement Order(s), it should not be less than 1!'))
983 if procurement.product_id.type in ('product', 'consu'):
984 if not procurement.move_id:
985 source = procurement.location_id.id
986 if procurement.procure_method=='make_to_order':
987 source = procurement.product_id.product_tmpl_id.property_stock_procurement.id
988 id = self.pool.get('stock.move').create(cr, uid, {
989 'name': 'PROC:'+procurement.name,
990 'location_id': source,
991 'location_dest_id': procurement.location_id.id,
992 'product_id': procurement.product_id.id,
993 'product_qty':procurement.product_qty,
994 'product_uom': procurement.product_uom.id,
995 'date_planned': procurement.date_planned,
998 self.write(cr, uid, [procurement.id], {'move_id': id, 'close_move':1})
1000 if procurement.procure_method=='make_to_stock' and procurement.move_id.state in ('draft','waiting',):
1001 # properly call action_confirm() on stock.move to abide by the location chaining etc.
1002 id = self.pool.get('stock.move').action_confirm(cr, uid, [procurement.move_id.id], context=context)
1003 self.write(cr, uid, ids, {'state':'confirmed','message':''})
1006 def action_move_assigned(self, cr, uid, ids, context={}):
1007 self.write(cr, uid, ids, {'state':'running','message':_('from stock: products assigned.')})
1010 def _check_make_to_stock_service(self, cr, uid, procurement, context={}):
1013 def _check_make_to_stock_product(self, cr, uid, procurement, context={}):
1015 if procurement.move_id:
1016 id = procurement.move_id.id
1017 if not (procurement.move_id.state in ('done','assigned','cancel')):
1018 ok = ok and self.pool.get('stock.move').action_assign(cr, uid, [id])
1019 cr.execute('select count(id) from stock_warehouse_orderpoint where product_id=%s', (procurement.product_id.id,))
1020 if not cr.fetchone()[0]:
1021 cr.execute('update mrp_procurement set message=%s where id=%s', (_('from stock and no minimum orderpoint rule defined'), procurement.id))
1024 def action_produce_assign_service(self, cr, uid, ids, context={}):
1025 for procurement in self.browse(cr, uid, ids):
1026 self.write(cr, uid, [procurement.id], {'state':'running'})
1029 def action_produce_assign_product(self, cr, uid, ids, context={}):
1031 company = self.pool.get('res.users').browse(cr, uid, uid, context).company_id
1032 for procurement in self.browse(cr, uid, ids):
1033 res_id = procurement.move_id.id
1034 loc_id = procurement.location_id.id
1035 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)
1036 newdate = newdate - DateTime.RelativeDateTime(days=company.manufacturing_lead)
1037 produce_id = self.pool.get('mrp.production').create(cr, uid, {
1038 'origin': procurement.origin,
1039 'product_id': procurement.product_id.id,
1040 'product_qty': procurement.product_qty,
1041 'product_uom': procurement.product_uom.id,
1042 'product_uos_qty': procurement.product_uos and procurement.product_uos_qty or False,
1043 'product_uos': procurement.product_uos and procurement.product_uos.id or False,
1044 'location_src_id': procurement.location_id.id,
1045 'location_dest_id': procurement.location_id.id,
1046 'bom_id': procurement.bom_id and procurement.bom_id.id or False,
1047 'date_planned': newdate.strftime('%Y-%m-%d %H:%M:%S'),
1048 'move_prod_id': res_id,
1050 self.write(cr, uid, [procurement.id], {'state':'running'})
1051 bom_result = self.pool.get('mrp.production').action_compute(cr, uid,
1052 [produce_id], properties=[x.id for x in procurement.property_ids])
1053 wf_service = netsvc.LocalService("workflow")
1054 wf_service.trg_validate(uid, 'mrp.production', produce_id, 'button_confirm', cr)
1055 self.pool.get('stock.move').write(cr, uid, [res_id],
1056 {'location_id':procurement.location_id.id})
1059 def action_po_assign(self, cr, uid, ids, context={}):
1061 company = self.pool.get('res.users').browse(cr, uid, uid, context).company_id
1062 for procurement in self.browse(cr, uid, ids):
1063 res_id = procurement.move_id.id
1064 partner = procurement.product_id.seller_ids[0].name
1065 partner_id = partner.id
1066 address_id = self.pool.get('res.partner').address_get(cr, uid, [partner_id], ['delivery'])['delivery']
1067 pricelist_id = partner.property_product_pricelist_purchase.id
1069 uom_id = procurement.product_id.uom_po_id.id
1071 qty = self.pool.get('product.uom')._compute_qty(cr, uid, procurement.product_uom.id, procurement.product_qty, uom_id)
1072 if procurement.product_id.seller_ids[0].qty:
1073 qty=max(qty,procurement.product_id.seller_ids[0].qty)
1075 price = self.pool.get('product.pricelist').price_get(cr, uid, [pricelist_id], procurement.product_id.id, qty, False, {'uom': uom_id})[pricelist_id]
1077 newdate = DateTime.strptime(procurement.date_planned, '%Y-%m-%d %H:%M:%S')
1078 newdate = newdate - DateTime.RelativeDateTime(days=company.po_lead)
1079 newdate = newdate - procurement.product_id.seller_ids[0].delay
1081 #Passing partner_id to context for purchase order line integrity of Line name
1082 context.update({'lang':partner.lang, 'partner_id':partner_id})
1084 product=self.pool.get('product.product').browse(cr,uid,procurement.product_id.id,context=context)
1087 'name': product.partner_ref,
1089 'product_id': procurement.product_id.id,
1090 'product_uom': uom_id,
1091 'price_unit': price,
1092 'date_planned': newdate.strftime('%Y-%m-%d %H:%M:%S'),
1093 'move_dest_id': res_id,
1094 'notes':product.description_purchase,
1097 taxes_ids = procurement.product_id.product_tmpl_id.supplier_taxes_id
1098 taxes = self.pool.get('account.fiscal.position').map_tax(cr, uid, partner.property_account_position, taxes_ids)
1100 'taxes_id':[(6,0,taxes)]
1102 purchase_id = self.pool.get('purchase.order').create(cr, uid, {
1103 'origin': procurement.origin,
1104 'partner_id': partner_id,
1105 'partner_address_id': address_id,
1106 'location_id': procurement.location_id.id,
1107 'pricelist_id': pricelist_id,
1108 'order_line': [(0,0,line)],
1109 'fiscal_position': partner.property_account_position and partner.property_account_position.id or False
1111 self.write(cr, uid, [procurement.id], {'state':'running', 'purchase_id':purchase_id})
1114 def action_cancel(self, cr, uid, ids):
1117 for proc in self.browse(cr, uid, ids):
1119 if proc.move_id.state not in ('done','cancel'):
1120 todo2.append(proc.move_id.id)
1122 if proc.move_id and proc.move_id.state=='waiting':
1123 todo.append(proc.move_id.id)
1125 self.pool.get('stock.move').action_cancel(cr, uid, todo2)
1127 self.pool.get('stock.move').write(cr, uid, todo, {'state':'assigned'})
1128 self.write(cr, uid, ids, {'state':'cancel'})
1129 wf_service = netsvc.LocalService("workflow")
1131 wf_service.trg_trigger(uid, 'mrp.procurement', id, cr)
1134 def action_check_finnished(self, cr, uid, ids):
1135 return self.check_move_done(cr, uid, ids)
1137 def action_check(self, cr, uid, ids):
1139 for procurement in self.browse(cr, uid, ids):
1140 if procurement.move_id.state=='assigned' or procurement.move_id.state=='done':
1141 self.action_done(cr, uid, [procurement.id])
1145 def action_ready(self, cr, uid, ids):
1146 res = self.write(cr, uid, ids, {'state':'ready'})
1149 def action_done(self, cr, uid, ids):
1150 for procurement in self.browse(cr, uid, ids):
1151 if procurement.move_id:
1152 if procurement.close_move and (procurement.move_id.state <> 'done'):
1153 self.pool.get('stock.move').action_done(cr, uid, [procurement.move_id.id])
1154 res = self.write(cr, uid, ids, {'state':'done', 'date_close':time.strftime('%Y-%m-%d')})
1155 wf_service = netsvc.LocalService("workflow")
1157 wf_service.trg_trigger(uid, 'mrp.procurement', id, cr)
1160 def run_scheduler(self, cr, uid, automatic=False, use_new_cursor=False, context=None):
1162 use_new_cursor: False or the dbname
1166 self._procure_confirm(cr, uid, use_new_cursor=use_new_cursor, context=context)
1167 self._procure_orderpoint_confirm(cr, uid, automatic=automatic,\
1168 use_new_cursor=use_new_cursor, context=context)
1172 class stock_warehouse_orderpoint(osv.osv):
1173 _name = "stock.warehouse.orderpoint"
1174 _description = "Orderpoint minimum rule"
1176 'name': fields.char('Name', size=32, required=True),
1177 'active': fields.boolean('Active'),
1178 'logic': fields.selection([('max','Order to Max'),('price','Best price (not yet active!)')], 'Reordering Mode', required=True),
1179 'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse', required=True, ondelete="cascade"),
1180 'location_id': fields.many2one('stock.location', 'Location', required=True, ondelete="cascade"),
1181 'product_id': fields.many2one('product.product', 'Product', required=True, domain=[('type','=','product')], ondelete="cascade"),
1182 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True),
1183 'product_min_qty': fields.float('Min Quantity', required=True,
1184 help="When the virtual stock goes belong the Min Quantity, Open ERP generates "\
1185 "a procurement to bring the virtual stock to the Max Quantity."),
1186 'product_max_qty': fields.float('Max Quantity', required=True,
1187 help="When the virtual stock goes belong the Min Quantity, Open ERP generates "\
1188 "a procurement to bring the virtual stock to the Max Quantity."),
1189 'qty_multiple': fields.integer('Qty Multiple', required=True,
1190 help="The procurement quantity will by rounded up to this multiple."),
1191 'procurement_id': fields.many2one('mrp.procurement', 'Purchase Order', ondelete="set null")
1194 'active': lambda *a: 1,
1195 'logic': lambda *a: 'max',
1196 'qty_multiple': lambda *a: 1,
1197 'name': lambda x,y,z,c: x.pool.get('ir.sequence').get(y,z,'mrp.warehouse.orderpoint') or '',
1198 'product_uom': lambda sel, cr, uid, context: context.get('product_uom', False),
1200 def onchange_warehouse_id(self, cr, uid, ids, warehouse_id, context={}):
1202 w=self.pool.get('stock.warehouse').browse(cr,uid,warehouse_id, context)
1203 v = {'location_id':w.lot_stock_id.id}
1206 def onchange_product_id(self, cr, uid, ids, product_id, context={}):
1208 prod=self.pool.get('product.product').browse(cr,uid,product_id)
1209 v = {'product_uom':prod.uom_id.id}
1212 def copy(self, cr, uid, id, default=None,context={}):
1216 'name': self.pool.get('ir.sequence').get(cr, uid, 'mrp.warehouse.orderpoint') or '',
1218 return super(stock_warehouse_orderpoint, self).copy(cr, uid, id, default, context)
1219 stock_warehouse_orderpoint()
1222 class StockMove(osv.osv):
1223 _inherit = 'stock.move'
1225 'procurements': fields.one2many('mrp.procurement', 'move_id', 'Procurements'),
1227 def copy(self, cr, uid, id, default=None, context=None):
1228 default = default or {}
1229 default['procurements'] = []
1230 return super(StockMove, self).copy(cr, uid, id, default, context)
1232 def _action_explode(self, cr, uid, move, context={}):
1233 if move.product_id.supply_method=='produce' and move.product_id.procure_method=='make_to_order':
1234 bis = self.pool.get('mrp.bom').search(cr, uid, [
1235 ('product_id','=',move.product_id.id),
1236 ('bom_id','=',False),
1237 ('type','=','phantom')])
1239 factor = move.product_qty
1240 bom_point = self.pool.get('mrp.bom').browse(cr, uid, bis[0])
1241 res = self.pool.get('mrp.bom')._bom_explode(cr, uid, bom_point, factor, [])
1242 dest = move.product_id.product_tmpl_id.property_stock_production.id
1244 if move.state=='assigned':
1248 'picking_id': move.picking_id.id,
1249 'product_id': line['product_id'],
1250 'product_uom': line['product_uom'],
1251 'product_qty': line['product_qty'],
1252 'product_uos': line['product_uos'],
1253 'product_uos_qty': line['product_uos_qty'],
1254 'move_dest_id': move.id,
1256 'name': line['name'],
1257 'location_dest_id': dest,
1258 'move_history_ids': [(6,0,[move.id])],
1259 'move_history_ids2': [(6,0,[])],
1262 mid = self.pool.get('stock.move').copy(cr, uid, move.id, default=valdef)
1263 prodobj = self.pool.get('product.product').browse(cr, uid, line['product_id'], context=context)
1264 proc_id = self.pool.get('mrp.procurement').create(cr, uid, {
1265 'name': (move.picking_id.origin or ''),
1266 'origin': (move.picking_id.origin or ''),
1267 'date_planned': move.date_planned,
1268 'product_id': line['product_id'],
1269 'product_qty': line['product_qty'],
1270 'product_uom': line['product_uom'],
1271 'product_uos_qty': line['product_uos'] and line['product_uos_qty'] or False,
1272 'product_uos': line['product_uos'],
1273 'location_id': move.location_id.id,
1274 'procure_method': prodobj.procure_method,
1277 wf_service = netsvc.LocalService("workflow")
1278 wf_service.trg_validate(uid, 'mrp.procurement', proc_id, 'button_confirm', cr)
1279 self.pool.get('stock.move').write(cr, uid, [move.id], {
1280 'location_id': move.location_dest_id.id,
1281 'auto_validate': True,
1282 'picking_id': False,
1283 'location_id': dest,
1286 for m in self.pool.get('mrp.procurement').search(cr, uid, [('move_id','=',move.id)], context):
1287 wf_service = netsvc.LocalService("workflow")
1288 wf_service.trg_validate(uid, 'mrp.procurement', m, 'button_wait_done', cr)
1293 class StockPicking(osv.osv):
1294 _inherit = 'stock.picking'
1296 def test_finnished(self, cursor, user, ids):
1297 wf_service = netsvc.LocalService("workflow")
1298 res = super(StockPicking, self).test_finnished(cursor, user, ids)
1299 for picking in self.browse(cursor, user, ids):
1300 for move in picking.move_lines:
1301 if move.state == 'done' and move.procurements:
1302 for procurement in move.procurements:
1303 wf_service.trg_validate(user, 'mrp.procurement',
1304 procurement.id, 'button_check', cursor)
1308 # Explode picking by replacing phantom BoMs
1310 def action_explode(self, cr, uid, picks, *args):
1311 for move in self.pool.get('stock.move').browse(cr, uid, picks):
1312 self.pool.get('stock.move')._action_explode(cr, uid, move)
1317 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: