1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
22 from osv import fields
28 from mx import DateTime
29 from tools.translate import _
31 #----------------------------------------------------------
33 #----------------------------------------------------------
34 # capacity_hour : capacity per hour. default: 1.0.
35 # Eg: If 5 concurrent operations at one time: capacity = 5 (because 5 employees)
36 # unit_per_cycle : how many units are produced for one cycle
38 # TODO: Work Center may be recursive ?
40 class mrp_workcenter(osv.osv):
41 _name = 'mrp.workcenter'
42 _description = 'Workcenter'
44 'name': fields.char('Workcenter Name', size=64, required=True),
45 'active': fields.boolean('Active'),
46 'type': fields.selection([('machine','Machine'),('hr','Human Resource'),('tool','Tool')], 'Type', required=True),
47 'code': fields.char('Code', size=16),
48 'timesheet_id': fields.many2one('hr.timesheet.group', 'Working Time', help="The normal working time of the workcenter."),
49 'note': fields.text('Description', help="Description of the workcenter. Explain here what's a cycle according to this workcenter."),
51 'capacity_per_cycle': fields.float('Capacity per Cycle', help="Number of operation this workcenter can do in parallel. If this workcenter represent a team of 5 workers, the capacity per cycle is 5."),
53 'time_cycle': fields.float('Time for 1 cycle (hour)', help="Time in hours for doing one cycle."),
54 'time_start': fields.float('Time before prod.', help="Time in hours for the setup."),
55 'time_stop': fields.float('Time after prod.', help="Time in hours for the cleaning."),
56 'time_efficiency': fields.float('Time Efficiency', help="Factor to adjust the work center cycle and before and after production durations"),
58 'costs_hour': fields.float('Cost per hour'),
59 'costs_hour_account_id': fields.many2one('account.analytic.account', 'Hour Account', domain=[('type','<>','view')],
60 help="Complete this only if you want automatic analytic accounting entries on production orders."),
61 'costs_cycle': fields.float('Cost per cycle'),
62 'costs_cycle_account_id': fields.many2one('account.analytic.account', 'Cycle Account', domain=[('type','<>','view')],
63 help="Complete this only if you want automatic analytic accounting entries on production orders."),
64 'costs_journal_id': fields.many2one('account.analytic.journal', 'Analytic Journal'),
65 'costs_general_account_id': fields.many2one('account.account', 'General Account', domain=[('type','<>','view')]),
68 'active': lambda *a: 1,
69 'type': lambda *a: 'machine',
70 'time_efficiency': lambda *a: 1.0,
71 'capacity_per_cycle': lambda *a: 1.0,
76 class mrp_property_group(osv.osv):
77 _name = 'mrp.property.group'
78 _description = 'Property Group'
80 'name': fields.char('Property Group', size=64, required=True),
81 'description': fields.text('Description'),
85 class mrp_property(osv.osv):
86 _name = 'mrp.property'
87 _description = 'Property'
89 'name': fields.char('Name', size=64, required=True),
90 'composition': fields.selection([('min','min'),('max','max'),('plus','plus')], 'Properties composition', required=True, help="Not used in computations, for information purpose only."),
91 'group_id': fields.many2one('mrp.property.group', 'Property Group', required=True),
92 'description': fields.text('Description'),
95 'composition': lambda *a: 'min',
99 class mrp_routing(osv.osv):
100 _name = 'mrp.routing'
101 _description = 'Routing'
103 'name': fields.char('Name', size=64, required=True),
104 'active': fields.boolean('Active'),
105 'code': fields.char('Code', size=8),
107 'note': fields.text('Description'),
108 'workcenter_lines': fields.one2many('mrp.routing.workcenter', 'routing_id', 'Workcenters'),
110 'location_id': fields.many2one('stock.location', 'Production Location',
111 help="Keep empty if you produce at the location where the finished products are needed." \
112 "Set a location if you produce at a fixed location. This can be a partner location " \
113 "if you subcontract the manufacturing operations."
117 'active': lambda *a: 1,
121 class mrp_routing_workcenter(osv.osv):
122 _name = 'mrp.routing.workcenter'
123 _description = 'Routing workcenter usage'
125 'workcenter_id': fields.many2one('mrp.workcenter', 'Workcenter', required=True),
126 'name': fields.char('Name', size=64, required=True),
127 'sequence': fields.integer('Sequence'),
128 'cycle_nbr': fields.float('Number of Cycle', required=True,
129 help="Time in hours for doing one cycle."),
130 'hour_nbr': fields.float('Number of Hours', required=True, help="Cost per hour"),
131 'routing_id': fields.many2one('mrp.routing', 'Parent Routing', select=True,
132 help="routing indicates all the workcenters used, for how long and/or cycles." \
133 "If Routing is indicated then,the third tab of a production order (workcenters) will be automatically pre-completed."),
134 'note': fields.text('Description')
137 'cycle_nbr': lambda *a: 1.0,
138 'hour_nbr': lambda *a: 0.0,
140 mrp_routing_workcenter()
142 class mrp_bom(osv.osv):
144 _description = 'Bill of Material'
145 def _child_compute(self, cr, uid, ids, name, arg, context={}):
147 for bom in self.browse(cr, uid, ids, context=context):
148 result[bom.id] = map(lambda x: x.id, bom.bom_lines)
151 ok = ((name=='child_complete_ids') and (bom.product_id.supply_method=='produce'))
152 if bom.type=='phantom' or ok:
153 sids = self.pool.get('mrp.bom').search(cr, uid, [('bom_id','=',False),('product_id','=',bom.product_id.id)])
155 bom2 = self.pool.get('mrp.bom').browse(cr, uid, sids[0], context=context)
156 result[bom.id] += map(lambda x: x.id, bom2.bom_lines)
158 def _compute_type(self, cr, uid, ids, field_name, arg, context):
159 res = dict(map(lambda x: (x,''), ids))
160 for line in self.browse(cr, uid, ids):
161 if line.type=='phantom' and not line.bom_id:
164 if line.bom_lines or line.type=='phantom':
166 if line.product_id.supply_method=='produce':
167 if line.product_id.procure_method=='make_to_stock':
168 res[line.id] = 'stock'
170 res[line.id] = 'order'
173 'name': fields.char('Name', size=64, required=True),
174 'code': fields.char('Code', size=16),
175 'active': fields.boolean('Active'),
176 'type': fields.selection([('normal','Normal BoM'),('phantom','Sets / Phantom')], 'BoM Type', required=True, help=
177 "Use a phantom bill of material in raw materials lines that have to be " \
178 "automatically computed in on eproduction order and not one per level." \
179 "If you put \"Phantom/Set\" at the root level of a bill of material " \
180 "it is considered as a set or pack: the products are replaced by the components " \
181 "between the sale order to the picking without going through the production order." \
182 "The normal BoM will generate one production order per BoM level."),
183 'method': fields.function(_compute_type, string='Method', method=True, type='selection', selection=[('',''),('stock','On Stock'),('order','On Order'),('set','Set / Pack')]),
184 'date_start': fields.date('Valid From', help="Validity of this BoM or component. Keep empty if it's always valid."),
185 'date_stop': fields.date('Valid Until', help="Validity of this BoM or component. Keep empty if it's always valid."),
186 'sequence': fields.integer('Sequence'),
187 'position': fields.char('Internal Ref.', size=64, help="Reference to a position in an external plan."),
188 'product_id': fields.many2one('product.product', 'Product', required=True),
189 'product_uos_qty': fields.float('Product UOS Qty'),
190 'product_uos': fields.many2one('product.uom', 'Product UOS', help="Product UOS (Unit of Sale) is the unit of measurement for the invoicing and promotion of stock."),
191 'product_qty': fields.float('Product Qty', required=True),
192 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True, help="UoM (Unit of Measure) is the unit of measurement for the inventory control"),
193 'product_rounding': fields.float('Product Rounding', help="Rounding applied on the product quantity. For integer only values, put 1.0"),
194 'product_efficiency': fields.float('Product Efficiency', required=True, help="Material efficiency. A factor of 0.9 means a loss of 10% in the production."),
195 'bom_lines': fields.one2many('mrp.bom', 'bom_id', 'BoM Lines'),
196 'bom_id': fields.many2one('mrp.bom', 'Parent BoM', ondelete='cascade', select=True),
197 '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."),
198 'property_ids': fields.many2many('mrp.property', 'mrp_bom_property_rel', 'bom_id','property_id', 'Properties'),
199 'revision_ids': fields.one2many('mrp.bom.revision', 'bom_id', 'BoM Revisions'),
200 'revision_type': fields.selection([('numeric','numeric indices'),('alpha','alphabetical indices')], 'Index type'),
201 'child_ids': fields.function(_child_compute,relation='mrp.bom', method=True, string="BoM Hierarchy", type='many2many'),
202 'child_complete_ids': fields.function(_child_compute,relation='mrp.bom', method=True, string="BoM Hierarchy", type='many2many')
205 'active': lambda *a: 1,
206 'product_efficiency': lambda *a: 1.0,
207 'product_qty': lambda *a: 1.0,
208 'product_rounding': lambda *a: 1.0,
209 'type': lambda *a: 'normal',
213 ('bom_qty_zero', 'CHECK (product_qty>0)', 'All product quantities must be greater than 0.\n' \
214 'You should install the mrp_subproduct module if you want to manage extra products on BoMs !'),
217 def _check_recursion(self, cr, uid, ids):
220 cr.execute('select distinct bom_id from mrp_bom where id =ANY(%s)',(ids,))
221 ids = filter(None, map(lambda x:x[0], cr.fetchall()))
227 (_check_recursion, 'Error ! You can not create recursive BoM.', ['parent_id'])
231 def onchange_product_id(self, cr, uid, ids, product_id, name, context={}):
233 prod=self.pool.get('product.product').browse(cr,uid,[product_id])[0]
234 v = {'product_uom':prod.uom_id.id}
236 v['name'] = prod.name
240 def _bom_find(self, cr, uid, product_id, product_uom, properties=[]):
242 # Why searching on BoM without parent ?
243 cr.execute('select id from mrp_bom where product_id=%s and bom_id is null order by sequence', (product_id,))
244 ids = map(lambda x: x[0], cr.fetchall())
247 for bom in self.pool.get('mrp.bom').browse(cr, uid, ids):
249 for prop_id in bom.property_ids:
250 if prop_id.id in properties:
252 if (prop>max_prop) or ((max_prop==0) and not result):
257 def _bom_explode(self, cr, uid, bom, factor, properties, addthis=False, level=0):
258 factor = factor / (bom.product_efficiency or 1.0)
259 factor = rounding(factor, bom.product_rounding)
260 if factor<bom.product_rounding:
261 factor = bom.product_rounding
265 if bom.type=='phantom' and not bom.bom_lines:
266 newbom = self._bom_find(cr, uid, bom.product_id.id, bom.product_uom.id, properties)
268 res = self._bom_explode(cr, uid, self.browse(cr, uid, [newbom])[0], factor*bom.product_qty, properties, addthis=True, level=level+10)
269 result = result + res[0]
270 result2 = result2 + res[1]
275 if addthis and not bom.bom_lines:
278 'name': bom.product_id.name,
279 'product_id': bom.product_id.id,
280 'product_qty': bom.product_qty * factor,
281 'product_uom': bom.product_uom.id,
282 'product_uos_qty': bom.product_uos and bom.product_uos_qty * factor or False,
283 'product_uos': bom.product_uos and bom.product_uos.id or False,
286 for wc_use in bom.routing_id.workcenter_lines:
287 wc = wc_use.workcenter_id
288 d, m = divmod(factor, wc_use.workcenter_id.capacity_per_cycle)
289 mult = (d + (m and 1.0 or 0.0))
290 cycle = mult * wc_use.cycle_nbr
292 'name': bom.routing_id.name,
293 'workcenter_id': wc.id,
294 'sequence': level+(wc_use.sequence or 0),
296 'hour': float(wc_use.hour_nbr*mult + (wc.time_start+wc.time_stop+cycle*wc.time_cycle) * (wc.time_efficiency or 1.0)),
298 for bom2 in bom.bom_lines:
299 res = self._bom_explode(cr, uid, bom2, factor, properties, addthis=True, level=level+10)
300 result = result + res[0]
301 result2 = result2 + res[1]
302 return result, result2
304 def set_indices(self, cr, uid, ids, context = {}):
305 if not ids or (ids and not ids[0]):
307 res = self.read(cr, uid, ids, ['revision_ids', 'revision_type'])
308 rev_ids = res[0]['revision_ids']
311 for rev_id in rev_ids:
312 if res[0]['revision_type'] == 'numeric':
313 self.pool.get('mrp.bom.revision').write(cr, uid, [rev_id], {'indice' : idx})
315 self.pool.get('mrp.bom.revision').write(cr, uid, [rev_id], {'indice' : "%c"%(idx+96,)})
321 class mrp_bom_revision(osv.osv):
322 _name = 'mrp.bom.revision'
323 _description = 'Bill of material revisions'
325 'name': fields.char('Modification name', size=64, required=True),
326 'description': fields.text('Description'),
327 'date': fields.date('Modification Date'),
328 'indice': fields.char('Revision', size=16),
329 'last_indice': fields.char('last indice', size=64),
330 'author_id': fields.many2one('res.users', 'Author'),
331 'bom_id': fields.many2one('mrp.bom', 'BoM', select=True),
335 'author_id': lambda x,y,z,c: z,
336 'date': lambda *a: time.strftime('%Y-%m-%d'),
344 return round(f / r) * r
346 class mrp_production(osv.osv):
347 _name = 'mrp.production'
348 _description = 'Production'
349 _date_name = 'date_planned'
351 def _get_sale_order(self,cr,uid,ids,field_name=False):
352 move_obj=self.pool.get('stock.move')
353 def get_parent_move(move_id):
354 move = move_obj.browse(cr,uid,move_id)
355 if move.move_dest_id:
356 return get_parent_move(move.move_dest_id.id)
358 productions=self.read(cr,uid,ids,['id','move_prod_id'])
360 for production in productions:
361 res[production['id']]=False
362 if production.get('move_prod_id',False):
363 parent_move_line=get_parent_move(production['move_prod_id'][0])
365 move = move_obj.browse(cr,uid,parent_move_line)
366 #TODO: fix me sale module can not be used here,
367 #as may be mrp can be installed without sale module
368 if field_name=='name':
369 res[production['id']]=move.sale_line_id and move.sale_line_id.order_id.name or False
370 if field_name=='client_order_ref':
371 res[production['id']]=move.sale_line_id and move.sale_line_id.order_id.client_order_ref or False
374 def _production_calc(self, cr, uid, ids, prop, unknow_none, context={}):
376 for prod in self.browse(cr, uid, ids, context=context):
381 for wc in prod.workcenter_lines:
382 result[prod.id]['hour_total'] += wc.hour
383 result[prod.id]['cycle_total'] += wc.cycle
386 def _production_date_end(self, cr, uid, ids, prop, unknow_none, context={}):
388 for prod in self.browse(cr, uid, ids, context=context):
389 result[prod.id] = prod.date_planned
392 def _production_date(self, cr, uid, ids, prop, unknow_none, context={}):
394 for prod in self.browse(cr, uid, ids, context=context):
395 result[prod.id] = prod.date_planned[:10]
398 def _sale_name_calc(self, cr, uid, ids, prop, unknow_none, unknow_dict):
399 return self._get_sale_order(cr,uid,ids,field_name='name')
401 def _sale_ref_calc(self, cr, uid, ids, prop, unknow_none, unknow_dict):
402 return self._get_sale_order(cr,uid,ids,field_name='client_order_ref')
405 'name': fields.char('Reference', size=64, required=True),
406 'origin': fields.char('Origin', size=64),
407 'priority': fields.selection([('0','Not urgent'),('1','Normal'),('2','Urgent'),('3','Very Urgent')], 'Priority'),
409 'product_id': fields.many2one('product.product', 'Product', required=True, domain=[('type','<>','service')]),
410 'product_qty': fields.float('Product Qty', required=True, states={'draft':[('readonly',False)]}, readonly=True),
411 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True, states={'draft':[('readonly',False)]}, readonly=True),
412 'product_uos_qty': fields.float('Product UoS Qty', states={'draft':[('readonly',False)]}, readonly=True),
413 'product_uos': fields.many2one('product.uom', 'Product UoS', states={'draft':[('readonly',False)]}, readonly=True),
415 'location_src_id': fields.many2one('stock.location', 'Raw Materials Location', required=True,
416 help="Location where the system will look for products used in raw materials."),
417 'location_dest_id': fields.many2one('stock.location', 'Finished Products Location', required=True,
418 help="Location where the system will stock the finished products."),
420 'date_planned_end': fields.function(_production_date_end, method=True, type='date', string='Scheduled End'),
421 'date_planned_date': fields.function(_production_date, method=True, type='date', string='Scheduled Date'),
422 'date_planned': fields.datetime('Scheduled date', required=True, select=1),
423 'date_start': fields.datetime('Start Date'),
424 'date_finnished': fields.datetime('End Date'),
426 'bom_id': fields.many2one('mrp.bom', 'Bill of Material', domain=[('bom_id','=',False)]),
427 'routing_id': fields.many2one('mrp.routing', string='Routing', on_delete='set null', 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."),
429 'picking_id': fields.many2one('stock.picking', 'Picking list', readonly=True,
430 help="This is the internal picking list of the raw material needed for the production plan"),
431 'move_prod_id': fields.many2one('stock.move', 'Move product', readonly=True),
432 'move_lines': fields.many2many('stock.move', 'mrp_production_move_ids', 'production_id', 'move_id', 'Products Consummed'),
434 'move_created_ids': fields.one2many('stock.move', 'production_id', 'Moves Created'),
435 'product_lines': fields.one2many('mrp.production.product.line', 'production_id', 'Scheduled goods'),
436 'workcenter_lines': fields.one2many('mrp.production.workcenter.line', 'production_id', 'Workcenters Utilisation'),
437 'state': fields.selection([('draft','Draft'),('picking_except', 'Picking Exception'),('confirmed','Waiting Goods'),('ready','Ready to Produce'),('in_production','In Production'),('cancel','Cancelled'),('done','Done')],'State', readonly=True,
438 help='When the production order is created the state is set to \'Draft\'.\n If the order is confirmed the state is set to \'Waiting Goods\'.\n If any exceptions are there, the state is set to \'Packing Exception\'.\
439 \nIf the stock is available then the state is set to \'Ready to Produce\'.\n When the production get started then the state is set to \'In Production\'.\n When the production is over, the state is set to \'Done\'.'),
440 'hour_total': fields.function(_production_calc, method=True, type='float', string='Total Hours', multi='workorder'),
441 'cycle_total': fields.function(_production_calc, method=True, type='float', string='Total Cycles', multi='workorder'),
443 'sale_name': fields.function(_sale_name_calc, method=True, type='char', string='Sale Name'),
444 'sale_ref': fields.function(_sale_ref_calc, method=True, type='char', string='Sale Ref'),
447 'priority': lambda *a: '1',
448 'state': lambda *a: 'draft',
449 'date_planned': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
450 'product_qty': lambda *a: 1.0,
451 'name': lambda x,y,z,c: x.pool.get('ir.sequence').get(y,z,'mrp.production') or '/',
453 _order = 'date_planned asc, priority desc';
454 def unlink(self, cr, uid, ids, context=None):
455 productions = self.read(cr, uid, ids, ['state'])
457 for s in productions:
458 if s['state'] in ['draft','cancel']:
459 unlink_ids.append(s['id'])
461 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Production Order(s) which are in %s State!' % s['state']))
462 return osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
464 def copy(self, cr, uid, id, default=None,context=None):
468 'name': self.pool.get('ir.sequence').get(cr, uid, 'mrp.production'),
470 'move_created_ids': [],
473 return super(mrp_production, self).copy(cr, uid, id, default, context)
475 def location_id_change(self, cr, uid, ids, src, dest, context={}):
479 return {'value': {'location_dest_id': src}}
482 def product_id_change(self, cr, uid, ids, product):
485 res = self.pool.get('product.product').read(cr, uid, [product], ['uom_id'])[0]
486 uom = res['uom_id'] and res['uom_id'][0]
487 result = {'product_uom':uom}
488 return {'value':result}
490 def bom_id_change(self, cr, uid, ids, product):
493 res = self.pool.get('mrp.bom').read(cr, uid, [product], ['routing_id'])[0]
494 routing_id = res['routing_id'] and res['routing_id'][0]
495 result = {'routing_id':routing_id}
496 return {'value':result}
498 def action_picking_except(self, cr, uid, ids):
499 self.write(cr, uid, ids, {'state':'picking_except'})
502 def action_compute(self, cr, uid, ids, properties=[]):
504 for production in self.browse(cr, uid, ids):
505 cr.execute('delete from mrp_production_product_line where production_id=%s', (production.id,))
506 cr.execute('delete from mrp_production_workcenter_line where production_id=%s', (production.id,))
507 bom_point = production.bom_id
508 bom_id = production.bom_id.id
510 bom_id = self.pool.get('mrp.bom')._bom_find(cr, uid, production.product_id.id, production.product_uom.id, properties)
512 bom_point = self.pool.get('mrp.bom').browse(cr, uid, bom_id)
513 routing_id = bom_point.routing_id.id or False
514 self.write(cr, uid, [production.id], {'bom_id': bom_id, 'routing_id': routing_id})
517 raise osv.except_osv(_('Error'), _("Couldn't find bill of material for product"))
519 #if bom_point.routing_id and bom_point.routing_id.location_id:
520 # self.write(cr, uid, [production.id], {'location_src_id': bom_point.routing_id.location_id.id})
522 factor = production.product_qty * production.product_uom.factor / bom_point.product_uom.factor
523 res = self.pool.get('mrp.bom')._bom_explode(cr, uid, bom_point, factor / bom_point.product_qty, properties)
527 line['production_id'] = production.id
528 self.pool.get('mrp.production.product.line').create(cr, uid, line)
529 for line in results2:
530 line['production_id'] = production.id
531 self.pool.get('mrp.production.workcenter.line').create(cr, uid, line)
534 def action_cancel(self, cr, uid, ids):
535 for production in self.browse(cr, uid, ids):
536 if production.move_created_ids:
537 self.pool.get('stock.move').action_cancel(cr, uid, [x.id for x in production.move_created_ids])
538 self.pool.get('stock.move').action_cancel(cr, uid, [x.id for x in production.move_lines])
539 self.write(cr, uid, ids, {'state':'cancel'}) #,'move_lines':[(6,0,[])]})
542 #XXX: may be a bug here; lot_lines are unreserved for a few seconds;
543 # between the end of the picking list and the call to this function
544 def action_ready(self, cr, uid, ids):
545 self.write(cr, uid, ids, {'state':'ready'})
546 for production in self.browse(cr, uid, ids):
547 if production.move_prod_id:
548 self.pool.get('stock.move').write(cr, uid, [production.move_prod_id.id],
549 {'location_id':production.location_dest_id.id})
552 #TODO Review materials in function in_prod and prod_end.
553 def action_production_end(self, cr, uid, ids):
555 for production in self.browse(cr, uid, ids):
556 for res in production.move_lines:
557 for move in production.move_created_ids:
558 #XXX must use the orm
559 cr.execute('INSERT INTO stock_move_history_ids \
560 (parent_id, child_id) VALUES (%s,%s)',
562 move_ids.append(res.id)
563 vals= {'state':'confirmed'}
564 new_moves = [x.id for x in production.move_created_ids]
565 self.pool.get('stock.move').write(cr, uid, new_moves, vals)
566 if not production.date_finnished:
567 self.write(cr, uid, [production.id],
568 {'date_finnished': time.strftime('%Y-%m-%d %H:%M:%S')})
569 self.pool.get('stock.move').check_assign(cr, uid, new_moves)
570 self.pool.get('stock.move').action_done(cr, uid, new_moves)
571 self._costs_generate(cr, uid, production)
572 self.pool.get('stock.move').action_done(cr, uid, move_ids)
573 self.write(cr, uid, ids, {'state': 'done'})
576 def _costs_generate(self, cr, uid, production):
578 for wc_line in production.workcenter_lines:
579 wc = wc_line.workcenter_id
580 if wc.costs_journal_id and wc.costs_general_account_id:
581 value = wc_line.hour * wc.costs_hour
582 account = wc.costs_hour_account_id.id
583 if value and account:
585 self.pool.get('account.analytic.line').create(cr, uid, {
586 'name': wc_line.name+' (H)',
588 'account_id': account,
589 'general_account_id': wc.costs_general_account_id.id,
590 'journal_id': wc.costs_journal_id.id,
593 if wc.costs_journal_id and wc.costs_general_account_id:
594 value = wc_line.cycle * wc.costs_cycle
595 account = wc.costs_cycle_account_id.id
596 if value and account:
598 self.pool.get('account.analytic.line').create(cr, uid, {
599 'name': wc_line.name+' (C)',
601 'account_id': account,
602 'general_account_id': wc.costs_general_account_id.id,
603 'journal_id': wc.costs_journal_id.id,
608 def action_in_production(self, cr, uid, ids):
610 for production in self.browse(cr, uid, ids):
611 for res in production.move_lines:
612 move_ids.append(res.id)
613 if not production.date_start:
614 self.write(cr, uid, [production.id],
615 {'date_start': time.strftime('%Y-%m-%d %H:%M:%S')})
616 self.pool.get('stock.move').action_done(cr, uid, move_ids)
617 self.write(cr, uid, ids, {'state': 'in_production'})
620 def test_if_product(self, cr, uid, ids):
622 for production in self.browse(cr, uid, ids):
623 if not production.product_lines:
624 if not self.action_compute(cr, uid, [production.id]):
628 def _get_auto_picking(self, cr, uid, production):
631 def action_confirm(self, cr, uid, ids):
634 for production in self.browse(cr, uid, ids):
635 if not production.product_lines:
636 self.action_compute(cr, uid, [production.id])
637 production = self.browse(cr, uid, [production.id])[0]
639 pick_type = 'internal'
641 if production.bom_id.routing_id and production.bom_id.routing_id.location_id:
642 routing_loc = production.bom_id.routing_id.location_id
643 if routing_loc.usage<>'internal':
645 address_id = routing_loc.address_id and routing_loc.address_id.id or False
646 routing_loc = routing_loc.id
647 picking_id = self.pool.get('stock.picking').create(cr, uid, {
648 'origin': (production.origin or '').split(':')[0] +':'+production.name,
652 'address_id': address_id,
653 'auto_picking': self._get_auto_picking(cr, uid, production),
656 source = production.product_id.product_tmpl_id.property_stock_production.id
658 'name':'PROD:'+production.name,
659 'date_planned': production.date_planned,
660 'product_id': production.product_id.id,
661 'product_qty': production.product_qty,
662 'product_uom': production.product_uom.id,
663 'product_uos_qty': production.product_uos and production.product_uos_qty or False,
664 'product_uos': production.product_uos and production.product_uos.id or False,
665 'location_id': source,
666 'location_dest_id': production.location_dest_id.id,
667 'move_dest_id': production.move_prod_id.id,
670 res_final_id = self.pool.get('stock.move').create(cr, uid, data)
672 self.write(cr, uid, [production.id], {'move_created_ids': [(6, 0, [res_final_id])]})
674 for line in production.product_lines:
676 newdate = production.date_planned
677 if line.product_id.type in ('product', 'consu'):
678 res_dest_id = self.pool.get('stock.move').create(cr, uid, {
679 'name':'PROD:'+production.name,
680 'date_planned': production.date_planned,
681 'product_id': line.product_id.id,
682 'product_qty': line.product_qty,
683 'product_uom': line.product_uom.id,
684 'product_uos_qty': line.product_uos and line.product_uos_qty or False,
685 'product_uos': line.product_uos and line.product_uos.id or False,
686 'location_id': routing_loc or production.location_src_id.id,
687 'location_dest_id': source,
688 'move_dest_id': res_final_id,
691 moves.append(res_dest_id)
692 move_id = self.pool.get('stock.move').create(cr, uid, {
693 'name':'PROD:'+production.name,
694 'picking_id':picking_id,
695 'product_id': line.product_id.id,
696 'product_qty': line.product_qty,
697 'product_uom': line.product_uom.id,
698 'product_uos_qty': line.product_uos and line.product_uos_qty or False,
699 'product_uos': line.product_uos and line.product_uos.id or False,
700 'date_planned': newdate,
701 'move_dest_id': res_dest_id,
702 'location_id': production.location_src_id.id,
703 'location_dest_id': routing_loc or production.location_src_id.id,
706 proc_id = self.pool.get('mrp.procurement').create(cr, uid, {
707 'name': (production.origin or '').split(':')[0] + ':' + production.name,
708 'origin': (production.origin or '').split(':')[0] + ':' + production.name,
709 'date_planned': newdate,
710 'product_id': line.product_id.id,
711 'product_qty': line.product_qty,
712 'product_uom': line.product_uom.id,
713 'product_uos_qty': line.product_uos and line.product_qty or False,
714 'product_uos': line.product_uos and line.product_uos.id or False,
715 'location_id': production.location_src_id.id,
716 'procure_method': line.product_id.procure_method,
719 wf_service = netsvc.LocalService("workflow")
720 wf_service.trg_validate(uid, 'mrp.procurement', proc_id, 'button_confirm', cr)
721 proc_ids.append(proc_id)
722 wf_service = netsvc.LocalService("workflow")
723 wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
724 self.write(cr, uid, [production.id], {'picking_id':picking_id, 'move_lines': [(6,0,moves)], 'state':'confirmed'})
727 def force_production(self, cr, uid, ids, *args):
728 pick_obj = self.pool.get('stock.picking')
729 pick_obj.force_assign(cr, uid, [prod.picking_id.id for prod in self.browse(cr, uid, ids)])
735 class stock_move(osv.osv):
737 _inherit = 'stock.move'
739 'production_id': fields.many2one('mrp.production', 'Production', select=True),
743 class mrp_production_workcenter_line(osv.osv):
744 _name = 'mrp.production.workcenter.line'
745 _description = 'Work Orders'
748 'name': fields.char('Work Order', size=64, required=True),
749 'workcenter_id': fields.many2one('mrp.workcenter', 'Workcenter', required=True),
750 'cycle': fields.float('Nbr of cycle', digits=(16,2)),
751 'hour': fields.float('Nbr of hour', digits=(16,2)),
752 'sequence': fields.integer('Sequence', required=True),
753 'production_id': fields.many2one('mrp.production', 'Production Order', select=True, ondelete='cascade'),
756 'sequence': lambda *a: 1,
757 'hour': lambda *a: 0,
758 'cycle': lambda *a: 0,
760 mrp_production_workcenter_line()
762 class mrp_production_product_line(osv.osv):
763 _name = 'mrp.production.product.line'
764 _description = 'Production scheduled products'
766 'name': fields.char('Name', size=64, required=True),
767 'product_id': fields.many2one('product.product', 'Product', required=True),
768 'product_qty': fields.float('Product Qty', required=True),
769 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True),
770 'product_uos_qty': fields.float('Product UOS Qty'),
771 'product_uos': fields.many2one('product.uom', 'Product UOS'),
772 'production_id': fields.many2one('mrp.production', 'Production Order', select=True),
774 mrp_production_product_line()
776 # ------------------------------------------------------------------
778 # ------------------------------------------------------------------
780 # Produce, Buy or Find products and place a move
781 # then wizard for picking lists & move
783 class mrp_procurement(osv.osv):
784 _name = "mrp.procurement"
785 _description = "Procurement"
786 _order = 'priority,date_planned'
788 'name': fields.char('Name', size=64, required=True),
789 'origin': fields.char('Origin', size=64,
790 help="Reference of the document that created this Requisition.\n"
791 "This is automatically completed by Open ERP."),
792 'priority': fields.selection([('0','Not urgent'),('1','Normal'),('2','Urgent'),('3','Very Urgent')], 'Priority', required=True),
793 'date_planned': fields.datetime('Scheduled date', required=True),
794 'date_close': fields.datetime('Date Closed'),
795 'product_id': fields.many2one('product.product', 'Product', required=True),
796 'product_qty': fields.float('Quantity', required=True, states={'draft':[('readonly',False)]}, readonly=True),
797 'product_uom': fields.many2one('product.uom', 'Product UoM', required=True, states={'draft':[('readonly',False)]}, readonly=True),
798 'product_uos_qty': fields.float('UoS Quantity', states={'draft':[('readonly',False)]}, readonly=True),
799 'product_uos': fields.many2one('product.uom', 'Product UoS', states={'draft':[('readonly',False)]}, readonly=True),
800 'move_id': fields.many2one('stock.move', 'Reservation', ondelete='set null'),
802 'bom_id': fields.many2one('mrp.bom', 'BoM', ondelete='cascade', select=True),
804 'close_move': fields.boolean('Close Move at end', required=True),
805 'location_id': fields.many2one('stock.location', 'Location', required=True),
806 'procure_method': fields.selection([('make_to_stock','from stock'),('make_to_order','on order')], 'Requisition Method', states={'draft':[('readonly',False)], 'confirmed':[('readonly',False)]},
807 readonly=True, required=True, help="If you encode manually a Requisition, you probably want to use" \
808 " a make to order method."),
810 'purchase_id': fields.many2one('purchase.order', 'Purchase Order'),
811 'note': fields.text('Note'),
813 'property_ids': fields.many2many('mrp.property', 'mrp_procurement_property_rel', 'procurement_id','property_id', 'Properties'),
815 'message': fields.char('Latest error', size=64),
816 'state': fields.selection([
818 ('confirmed','Confirmed'),
819 ('exception','Exception'),
820 ('running','Running'),
824 ('waiting','Waiting')], 'State', required=True,
825 help='When a procurement is created the state is set to \'Draft\'.\n If the procurement is confirmed, the state is set to \'Confirmed\'.\
826 \nAfter confirming the state is set to \'Running\'.\n If any exception arises in the order then the state is set to \'Exception\'.\n Once the exception is removed the state becomes \'Ready\'.\n It is in \'Waiting\'. state when the procurement is waiting for another one to finish.'),
827 'note' : fields.text('Note'),
830 'state': lambda *a: 'draft',
831 'priority': lambda *a: '1',
832 'date_planned': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
833 'close_move': lambda *a: 0,
834 'procure_method': lambda *a: 'make_to_order',
837 def unlink(self, cr, uid, ids, context=None):
838 procurements = self.read(cr, uid, ids, ['state'])
840 for s in procurements:
841 if s['state'] in ['draft','cancel']:
842 unlink_ids.append(s['id'])
844 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Requisition Order(s) which are in %s State!' % s['state']))
845 return osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
847 def onchange_product_id(self, cr, uid, ids, product_id, context={}):
849 w=self.pool.get('product.product').browse(cr,uid,product_id, context)
851 'product_uom':w.uom_id.id,
852 'product_uos':w.uos_id and w.uos_id.id or w.uom_id.id
857 def check_product(self, cr, uid, ids):
858 for procurement in self.browse(cr, uid, ids):
859 if procurement.product_id.type in ('product', 'consu'):
863 def check_move_cancel(self, cr, uid, ids, context={}):
866 for procurement in self.browse(cr, uid, ids, context):
867 if procurement.move_id:
869 if not procurement.move_id.state=='cancel':
873 def check_move_done(self, cr, uid, ids, context={}):
875 for proc in self.browse(cr, uid, ids, context):
877 if not proc.move_id.state=='done':
882 # This method may be overrided by objects that override mrp.procurment
883 # for computing their own purpose
885 def _quantity_compute_get(self, cr, uid, proc, context={}):
886 if proc.product_id.type=='product':
887 if proc.move_id.product_uos:
888 return proc.move_id.product_uos_qty
891 def _uom_compute_get(self, cr, uid, proc, context={}):
892 if proc.product_id.type=='product':
893 if proc.move_id.product_uos:
894 return proc.move_id.product_uos.id
898 # Return the quantity of product shipped/produced/served, wich may be
899 # different from the planned quantity
901 def quantity_get(self, cr, uid, id, context={}):
902 proc = self.browse(cr, uid, id, context)
903 result = self._quantity_compute_get(cr, uid, proc, context)
905 result = proc.product_qty
908 def uom_get(self, cr, uid, id, context=None):
909 proc = self.browse(cr, uid, id, context)
910 result = self._uom_compute_get(cr, uid, proc, context)
912 result = proc.product_uom.id
915 def check_waiting(self, cr, uid, ids, context=[]):
916 for procurement in self.browse(cr, uid, ids, context=context):
917 if procurement.move_id and procurement.move_id.state=='auto':
921 def check_produce_service(self, cr, uid, procurement, context=[]):
924 def check_produce_product(self, cr, uid, procurement, context=[]):
925 properties = [x.id for x in procurement.property_ids]
926 bom_id = self.pool.get('mrp.bom')._bom_find(cr, uid, procurement.product_id.id, procurement.product_uom.id, properties)
928 cr.execute('update mrp_procurement set message=%s where id=%s', (_('No BoM defined for this product !'), procurement.id))
932 def check_make_to_stock(self, cr, uid, ids, context={}):
934 for procurement in self.browse(cr, uid, ids, context=context):
935 if procurement.product_id.type=='service':
936 ok = ok and self._check_make_to_stock_service(cr, uid, procurement, context)
938 ok = ok and self._check_make_to_stock_product(cr, uid, procurement, context)
941 def check_produce(self, cr, uid, ids, context={}):
943 user = self.pool.get('res.users').browse(cr, uid, uid)
944 for procurement in self.browse(cr, uid, ids):
945 if procurement.product_id.product_tmpl_id.supply_method<>'produce':
946 if procurement.product_id.seller_ids:
947 partner = procurement.product_id.seller_ids[0].name
948 if user.company_id and user.company_id.partner_id:
949 if partner.id == user.company_id.partner_id.id:
952 if procurement.product_id.product_tmpl_id.type=='service':
953 res = res and self.check_produce_service(cr, uid, procurement, context)
955 res = res and self.check_produce_product(cr, uid, procurement, context)
960 def check_buy(self, cr, uid, ids):
961 user = self.pool.get('res.users').browse(cr, uid, uid)
962 for procurement in self.browse(cr, uid, ids):
963 if procurement.product_id.product_tmpl_id.supply_method<>'buy':
965 if not procurement.product_id.seller_ids:
966 cr.execute('update mrp_procurement set message=%s where id=%s', (_('No supplier defined for this product !'), procurement.id))
968 partner = procurement.product_id.seller_ids[0].name
969 if user.company_id and user.company_id.partner_id:
970 if partner.id == user.company_id.partner_id.id:
972 address_id = self.pool.get('res.partner').address_get(cr, uid, [partner.id], ['delivery'])['delivery']
974 cr.execute('update mrp_procurement set message=%s where id=%s', (_('No address defined for the supplier'), procurement.id))
978 def test_cancel(self, cr, uid, ids):
979 for record in self.browse(cr, uid, ids):
980 if record.move_id and record.move_id.state=='cancel':
984 def action_confirm(self, cr, uid, ids, context={}):
985 for procurement in self.browse(cr, uid, ids):
986 if procurement.product_qty <= 0.00:
987 raise osv.except_osv(_('Data Insufficient !'), _('Please check the Quantity of Requisition Order(s), it should not be less than 1!'))
988 if procurement.product_id.type in ('product', 'consu'):
989 if not procurement.move_id:
990 source = procurement.location_id.id
991 if procurement.procure_method=='make_to_order':
992 source = procurement.product_id.product_tmpl_id.property_stock_procurement.id
993 id = self.pool.get('stock.move').create(cr, uid, {
994 'name': 'PROC:'+procurement.name,
995 'location_id': source,
996 'location_dest_id': procurement.location_id.id,
997 'product_id': procurement.product_id.id,
998 'product_qty':procurement.product_qty,
999 'product_uom': procurement.product_uom.id,
1000 'date_planned': procurement.date_planned,
1001 'state':'confirmed',
1003 self.write(cr, uid, [procurement.id], {'move_id': id, 'close_move':1})
1006 if procurement.procure_method=='make_to_stock' and procurement.move_id.state in ('waiting',):
1007 id = self.pool.get('stock.move').write(cr, uid, [procurement.move_id.id], {'state':'confirmed'})
1008 self.write(cr, uid, ids, {'state':'confirmed','message':''})
1011 def action_move_assigned(self, cr, uid, ids):
1012 self.write(cr, uid, ids, {'state':'running','message':_('from stock: products assigned.')})
1015 def _check_make_to_stock_service(self, cr, uid, procurement, context={}):
1018 def _check_make_to_stock_product(self, cr, uid, procurement, context={}):
1020 if procurement.move_id:
1021 id = procurement.move_id.id
1022 if not (procurement.move_id.state in ('done','assigned','cancel')):
1023 ok = ok and self.pool.get('stock.move').action_assign(cr, uid, [id])
1024 cr.execute('select count(id) from stock_warehouse_orderpoint where product_id=%s', (procurement.product_id.id,))
1025 if not cr.fetchone()[0]:
1026 cr.execute('update mrp_procurement set message=%s where id=%s', (_('from stock and no minimum orderpoint rule defined'), procurement.id))
1029 def action_produce_assign_service(self, cr, uid, ids, context={}):
1030 for procurement in self.browse(cr, uid, ids):
1031 self.write(cr, uid, [procurement.id], {'state':'running'})
1034 def action_produce_assign_product(self, cr, uid, ids, context={}):
1036 company = self.pool.get('res.users').browse(cr, uid, uid, context).company_id
1037 for procurement in self.browse(cr, uid, ids):
1038 res_id = procurement.move_id.id
1039 loc_id = procurement.location_id.id
1040 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)
1041 newdate = newdate - DateTime.RelativeDateTime(days=company.manufacturing_lead)
1042 produce_id = self.pool.get('mrp.production').create(cr, uid, {
1043 'origin': procurement.origin,
1044 'product_id': procurement.product_id.id,
1045 'product_qty': procurement.product_qty,
1046 'product_uom': procurement.product_uom.id,
1047 'product_uos_qty': procurement.product_uos and procurement.product_uos_qty or False,
1048 'product_uos': procurement.product_uos and procurement.product_uos.id or False,
1049 'location_src_id': procurement.location_id.id,
1050 'location_dest_id': procurement.location_id.id,
1051 'bom_id': procurement.bom_id and procurement.bom_id.id or False,
1052 'date_planned': newdate.strftime('%Y-%m-%d %H:%M:%S'),
1053 'move_prod_id': res_id,
1055 self.write(cr, uid, [procurement.id], {'state':'running'})
1056 bom_result = self.pool.get('mrp.production').action_compute(cr, uid,
1057 [produce_id], properties=[x.id for x in procurement.property_ids])
1058 wf_service = netsvc.LocalService("workflow")
1059 wf_service.trg_validate(uid, 'mrp.production', produce_id, 'button_confirm', cr)
1060 self.pool.get('stock.move').write(cr, uid, [res_id],
1061 {'location_id':procurement.location_id.id})
1064 def action_po_assign(self, cr, uid, ids, context={}):
1066 company = self.pool.get('res.users').browse(cr, uid, uid, context).company_id
1067 for procurement in self.browse(cr, uid, ids):
1068 res_id = procurement.move_id.id
1069 partner = procurement.product_id.seller_ids[0].name
1070 partner_id = partner.id
1071 address_id = self.pool.get('res.partner').address_get(cr, uid, [partner_id], ['delivery'])['delivery']
1072 pricelist_id = partner.property_product_pricelist_purchase.id
1074 uom_id = procurement.product_id.uom_po_id.id
1076 qty = self.pool.get('product.uom')._compute_qty(cr, uid, procurement.product_uom.id, procurement.product_qty, uom_id)
1077 if procurement.product_id.seller_ids[0].qty:
1078 qty=max(qty,procurement.product_id.seller_ids[0].qty)
1080 price = self.pool.get('product.pricelist').price_get(cr, uid, [pricelist_id], procurement.product_id.id, qty, False, {'uom': uom_id})[pricelist_id]
1082 newdate = DateTime.strptime(procurement.date_planned, '%Y-%m-%d %H:%M:%S')
1083 newdate = newdate - DateTime.RelativeDateTime(days=company.po_lead)
1084 newdate = newdate - procurement.product_id.seller_ids[0].delay
1086 context.update({'lang':partner.lang})
1087 product=self.pool.get('product.product').browse(cr,uid,procurement.product_id.id,context=context)
1090 'name': product.name,
1092 'product_id': procurement.product_id.id,
1093 'product_uom': uom_id,
1094 'price_unit': price,
1095 'date_planned': newdate.strftime('%Y-%m-%d %H:%M:%S'),
1096 'move_dest_id': res_id,
1097 'notes':product.description_purchase,
1100 taxes_ids = procurement.product_id.product_tmpl_id.supplier_taxes_id
1101 taxes = self.pool.get('account.fiscal.position').map_tax(cr, uid, partner.property_account_position, taxes_ids)
1103 'taxes_id':[(6,0,taxes)]
1105 purchase_id = self.pool.get('purchase.order').create(cr, uid, {
1106 'origin': procurement.origin,
1107 'partner_id': partner_id,
1108 'partner_address_id': address_id,
1109 'location_id': procurement.location_id.id,
1110 'pricelist_id': pricelist_id,
1111 'order_line': [(0,0,line)],
1112 'fiscal_position': partner.property_account_position and partner.property_account_position.id or False
1114 self.write(cr, uid, [procurement.id], {'state':'running', 'purchase_id':purchase_id})
1117 def action_cancel(self, cr, uid, ids):
1120 for proc in self.browse(cr, uid, ids):
1122 if proc.move_id.state not in ('done','cancel'):
1123 todo2.append(proc.move_id.id)
1125 if proc.move_id and proc.move_id.state=='waiting':
1126 todo.append(proc.move_id.id)
1128 self.pool.get('stock.move').action_cancel(cr, uid, todo2)
1130 self.pool.get('stock.move').write(cr, uid, todo, {'state':'assigned'})
1131 self.write(cr, uid, ids, {'state':'cancel'})
1132 wf_service = netsvc.LocalService("workflow")
1134 wf_service.trg_trigger(uid, 'mrp.procurement', id, cr)
1137 def action_check_finnished(self, cr, uid, ids):
1138 return self.check_move_done(cr, uid, ids)
1140 def action_check(self, cr, uid, ids):
1142 for procurement in self.browse(cr, uid, ids):
1143 if procurement.move_id.state=='assigned' or procurement.move_id.state=='done':
1144 self.action_done(cr, uid, [procurement.id])
1148 def action_ready(self, cr, uid, ids):
1149 res = self.write(cr, uid, ids, {'state':'ready'})
1152 def action_done(self, cr, uid, ids):
1153 for procurement in self.browse(cr, uid, ids):
1154 if procurement.move_id:
1155 if procurement.close_move and (procurement.move_id.state <> 'done'):
1156 self.pool.get('stock.move').action_done(cr, uid, [procurement.move_id.id])
1157 res = self.write(cr, uid, ids, {'state':'done', 'date_close':time.strftime('%Y-%m-%d')})
1158 wf_service = netsvc.LocalService("workflow")
1160 wf_service.trg_trigger(uid, 'mrp.procurement', id, cr)
1163 def run_scheduler(self, cr, uid, automatic=False, use_new_cursor=False, context=None):
1165 use_new_cursor: False or the dbname
1169 self._procure_confirm(cr, uid, use_new_cursor=use_new_cursor, context=context)
1170 self._procure_orderpoint_confirm(cr, uid, automatic=automatic,\
1171 use_new_cursor=use_new_cursor, context=context)
1175 class stock_warehouse_orderpoint(osv.osv):
1176 _name = "stock.warehouse.orderpoint"
1177 _description = "Orderpoint minimum rule"
1179 'name': fields.char('Name', size=32, required=True),
1180 'active': fields.boolean('Active'),
1181 'logic': fields.selection([('max','Order to Max'),('price','Best price (not yet active!)')], 'Reordering Mode', required=True),
1182 'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse', required=True),
1183 'location_id': fields.many2one('stock.location', 'Location', required=True),
1184 'product_id': fields.many2one('product.product', 'Product', required=True, domain=[('type','=','product')]),
1185 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True ),
1186 'product_min_qty': fields.float('Min Quantity', required=True,
1187 help="When the virtual stock goes belong the Min Quantity, Open ERP generates "\
1188 "a requisition to bring the virtual stock to the Max Quantity."),
1189 'product_max_qty': fields.float('Max Quantity', required=True,
1190 help="When the virtual stock goes belong the Min Quantity, Open ERP generates "\
1191 "a requisition to bring the virtual stock to the Max Quantity."),
1192 'qty_multiple': fields.integer('Qty Multiple', required=True,
1193 help="The requisition quantity will by rounded up to this multiple."),
1194 'procurement_id': fields.many2one('mrp.procurement', 'Purchase Order')
1197 'active': lambda *a: 1,
1198 'logic': lambda *a: 'max',
1199 'qty_multiple': lambda *a: 1,
1200 'name': lambda x,y,z,c: x.pool.get('ir.sequence').get(y,z,'mrp.warehouse.orderpoint') or '',
1201 'product_uom': lambda sel, cr, uid, context: context.get('product_uom', False),
1203 def onchange_warehouse_id(self, cr, uid, ids, warehouse_id, context={}):
1205 w=self.pool.get('stock.warehouse').browse(cr,uid,warehouse_id, context)
1206 v = {'location_id':w.lot_stock_id.id}
1209 def onchange_product_id(self, cr, uid, ids, product_id, context={}):
1211 prod=self.pool.get('product.product').browse(cr,uid,product_id)
1212 v = {'product_uom':prod.uom_id.id}
1215 def copy(self, cr, uid, id, default=None,context={}):
1219 'name': self.pool.get('ir.sequence').get(cr, uid, 'mrp.warehouse.orderpoint') or '',
1221 return super(stock_warehouse_orderpoint, self).copy(cr, uid, id, default, context)
1222 stock_warehouse_orderpoint()
1225 class StockMove(osv.osv):
1226 _inherit = 'stock.move'
1228 'procurements': fields.one2many('mrp.procurement', 'move_id', 'Requisitions'),
1230 def copy(self, cr, uid, id, default=None, context=None):
1231 default = default or {}
1232 default['procurements'] = []
1233 return super(StockMove, self).copy(cr, uid, id, default, context)
1235 def _action_explode(self, cr, uid, move, context={}):
1236 if move.product_id.supply_method=='produce' and move.product_id.procure_method=='make_to_order':
1237 bis = self.pool.get('mrp.bom').search(cr, uid, [
1238 ('product_id','=',move.product_id.id),
1239 ('bom_id','=',False),
1240 ('type','=','phantom')])
1242 factor = move.product_qty
1243 bom_point = self.pool.get('mrp.bom').browse(cr, uid, bis[0])
1244 res = self.pool.get('mrp.bom')._bom_explode(cr, uid, bom_point, factor, [])
1245 dest = move.product_id.product_tmpl_id.property_stock_production.id
1247 if move.state=='assigned':
1251 'picking_id': move.picking_id.id,
1252 'product_id': line['product_id'],
1253 'product_uom': line['product_uom'],
1254 'product_qty': line['product_qty'],
1255 'product_uos': line['product_uos'],
1256 'product_uos_qty': line['product_uos_qty'],
1257 'move_dest_id': move.id,
1259 'name': line['name'],
1260 'location_dest_id': dest,
1261 'move_history_ids': [(6,0,[move.id])],
1262 'move_history_ids2': [(6,0,[])],
1265 mid = self.pool.get('stock.move').copy(cr, uid, move.id, default=valdef)
1266 prodobj = self.pool.get('product.product').browse(cr, uid, line['product_id'], context=context)
1267 proc_id = self.pool.get('mrp.procurement').create(cr, uid, {
1268 'name': (move.picking_id.origin or ''),
1269 'origin': (move.picking_id.origin or ''),
1270 'date_planned': move.date_planned,
1271 'product_id': line['product_id'],
1272 'product_qty': line['product_qty'],
1273 'product_uom': line['product_uom'],
1274 'product_uos_qty': line['product_uos'] and line['product_uos_qty'] or False,
1275 'product_uos': line['product_uos'],
1276 'location_id': move.location_id.id,
1277 'procure_method': prodobj.procure_method,
1280 wf_service = netsvc.LocalService("workflow")
1281 wf_service.trg_validate(uid, 'mrp.procurement', proc_id, 'button_confirm', cr)
1282 self.pool.get('stock.move').write(cr, uid, [move.id], {
1283 'location_id': move.location_dest_id.id,
1284 'auto_validate': True,
1285 'picking_id': False,
1286 'location_id': dest,
1289 for m in self.pool.get('mrp.procurement').search(cr, uid, [('move_id','=',move.id)], context):
1290 wf_service = netsvc.LocalService("workflow")
1291 wf_service.trg_validate(uid, 'mrp.procurement', m, 'button_wait_done', cr)
1296 class StockPicking(osv.osv):
1297 _inherit = 'stock.picking'
1299 def test_finnished(self, cursor, user, ids):
1300 wf_service = netsvc.LocalService("workflow")
1301 res = super(StockPicking, self).test_finnished(cursor, user, ids)
1302 for picking in self.browse(cursor, user, ids):
1303 for move in picking.move_lines:
1304 if move.state == 'done' and move.procurements:
1305 for procurement in move.procurements:
1306 wf_service.trg_validate(user, 'mrp.procurement',
1307 procurement.id, 'button_check', cursor)
1311 # Explode picking by replacing phantom BoMs
1313 def action_explode(self, cr, uid, picks, *args):
1314 for move in self.pool.get('stock.move').browse(cr, uid, picks):
1315 self.pool.get('stock.move')._action_explode(cr, uid, move)
1320 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: