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 %s', (tuple(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
305 class mrp_bom_revision(osv.osv):
306 _name = 'mrp.bom.revision'
307 _description = 'Bill of material revisions'
309 'name': fields.char('Modification name', size=64, required=True),
310 'description': fields.text('Description'),
311 'date': fields.date('Modification Date'),
312 'indice': fields.char('Revision', size=16),
313 'last_indice': fields.char('last indice', size=64),
314 'author_id': fields.many2one('res.users', 'Author'),
315 'bom_id': fields.many2one('mrp.bom', 'BoM', select=True),
319 'author_id': lambda x,y,z,c: z,
320 'date': lambda *a: time.strftime('%Y-%m-%d'),
328 return round(f / r) * r
330 class mrp_production(osv.osv):
331 _name = 'mrp.production'
332 _description = 'Production'
333 _date_name = 'date_planned'
335 def _production_calc(self, cr, uid, ids, prop, unknow_none, context={}):
337 for prod in self.browse(cr, uid, ids, context=context):
342 for wc in prod.workcenter_lines:
343 result[prod.id]['hour_total'] += wc.hour
344 result[prod.id]['cycle_total'] += wc.cycle
347 def _production_date_end(self, cr, uid, ids, prop, unknow_none, context={}):
349 for prod in self.browse(cr, uid, ids, context=context):
350 result[prod.id] = prod.date_planned
353 def _production_date(self, cr, uid, ids, prop, unknow_none, context={}):
355 for prod in self.browse(cr, uid, ids, context=context):
356 result[prod.id] = prod.date_planned[:10]
360 'name': fields.char('Reference', size=64, required=True),
361 'origin': fields.char('Origin', size=64),
362 'priority': fields.selection([('0','Not urgent'),('1','Normal'),('2','Urgent'),('3','Very Urgent')], 'Priority'),
364 'product_id': fields.many2one('product.product', 'Product', required=True, domain=[('type','<>','service')]),
365 'product_qty': fields.float('Product Qty', required=True, states={'draft':[('readonly',False)]}, readonly=True),
366 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True, states={'draft':[('readonly',False)]}, readonly=True),
367 'product_uos_qty': fields.float('Product UoS Qty', states={'draft':[('readonly',False)]}, readonly=True),
368 'product_uos': fields.many2one('product.uom', 'Product UoS', states={'draft':[('readonly',False)]}, readonly=True),
370 'location_src_id': fields.many2one('stock.location', 'Raw Materials Location', required=True,
371 help="Location where the system will look for products used in raw materials."),
372 'location_dest_id': fields.many2one('stock.location', 'Finished Products Location', required=True,
373 help="Location where the system will stock the finished products."),
375 'date_planned_end': fields.function(_production_date_end, method=True, type='date', string='Scheduled End'),
376 'date_planned_date': fields.function(_production_date, method=True, type='date', string='Scheduled Date'),
377 'date_planned': fields.datetime('Scheduled date', required=True, select=1),
378 'date_start': fields.datetime('Start Date'),
379 'date_finnished': fields.datetime('End Date'),
381 'bom_id': fields.many2one('mrp.bom', 'Bill of Material', domain=[('bom_id','=',False)]),
382 'routing_id': fields.many2one('mrp.routing', string='Routing', on_delete='set null'),
384 'picking_id': fields.many2one('stock.picking', 'Packing list', readonly=True,
385 help="This is the internal picking list take bring the raw materials to the production plan."),
386 'move_prod_id': fields.many2one('stock.move', 'Move product', readonly=True),
387 'move_lines': fields.many2many('stock.move', 'mrp_production_move_ids', 'production_id', 'move_id', 'Products Consummed'),
389 'move_created_ids': fields.one2many('stock.move', 'production_id', 'Moves Created'),
390 'product_lines': fields.one2many('mrp.production.product.line', 'production_id', 'Scheduled goods'),
391 'workcenter_lines': fields.one2many('mrp.production.workcenter.line', 'production_id', 'Workcenters Utilisation'),
393 '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),
394 'hour_total': fields.function(_production_calc, method=True, type='float', string='Total Hours', multi='workorder'),
395 'cycle_total': fields.function(_production_calc, method=True, type='float', string='Total Cycles', multi='workorder'),
399 'priority': lambda *a: '1',
400 'state': lambda *a: 'draft',
401 'date_planned': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
402 'product_qty': lambda *a: 1.0,
403 'name': lambda x,y,z,c: x.pool.get('ir.sequence').get(y,z,'mrp.production') or '/',
405 _order = 'date_planned asc, priority desc';
406 def unlink(self, cr, uid, ids, context=None):
407 productions = self.read(cr, uid, ids, ['state'])
409 for s in productions:
410 if s['state'] in ['draft','cancel']:
411 unlink_ids.append(s['id'])
413 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Production Order(s) which are in %s State!' % s['state']))
414 return osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
416 def copy(self, cr, uid, id, default=None,context=None):
420 'name': self.pool.get('ir.sequence').get(cr, uid, 'mrp.production'),
422 'move_created_ids': [],
425 return super(mrp_production, self).copy(cr, uid, id, default, context)
427 def location_id_change(self, cr, uid, ids, src, dest, context={}):
431 return {'value': {'location_dest_id': src}}
434 def product_id_change(self, cr, uid, ids, product):
437 res = self.pool.get('product.product').read(cr, uid, [product], ['uom_id'])[0]
438 uom = res['uom_id'] and res['uom_id'][0]
439 result = {'product_uom':uom}
440 return {'value':result}
442 def bom_id_change(self, cr, uid, ids, product):
445 res = self.pool.get('mrp.bom').read(cr, uid, [product], ['routing_id'])[0]
446 routing_id = res['routing_id'] and res['routing_id'][0]
447 result = {'routing_id':routing_id}
448 return {'value':result}
450 def action_picking_except(self, cr, uid, ids):
451 self.write(cr, uid, ids, {'state':'picking_except'})
454 def action_compute(self, cr, uid, ids, properties=[]):
456 for production in self.browse(cr, uid, ids):
457 cr.execute('delete from mrp_production_product_line where production_id=%s', (production.id,))
458 cr.execute('delete from mrp_production_workcenter_line where production_id=%s', (production.id,))
459 bom_point = production.bom_id
460 bom_id = production.bom_id.id
462 bom_id = self.pool.get('mrp.bom')._bom_find(cr, uid, production.product_id.id, production.product_uom.id, properties)
464 bom_point = self.pool.get('mrp.bom').browse(cr, uid, bom_id)
465 routing_id = bom_point.routing_id.id or False
466 self.write(cr, uid, [production.id], {'bom_id': bom_id, 'routing_id': routing_id})
469 raise osv.except_osv(_('Error'), _("Couldn't find bill of material for product"))
471 #if bom_point.routing_id and bom_point.routing_id.location_id:
472 # self.write(cr, uid, [production.id], {'location_src_id': bom_point.routing_id.location_id.id})
474 factor = production.product_qty * production.product_uom.factor_inv * bom_point.product_uom.factor
475 res = self.pool.get('mrp.bom')._bom_explode(cr, uid, bom_point, factor / bom_point.product_qty, properties)
479 line['production_id'] = production.id
480 self.pool.get('mrp.production.product.line').create(cr, uid, line)
481 for line in results2:
482 line['production_id'] = production.id
483 self.pool.get('mrp.production.workcenter.line').create(cr, uid, line)
486 def action_cancel(self, cr, uid, ids):
487 for production in self.browse(cr, uid, ids):
488 if production.move_created_ids:
489 self.pool.get('stock.move').action_cancel(cr, uid, [x.id for x in production.move_created_ids])
490 self.pool.get('stock.move').action_cancel(cr, uid, [x.id for x in production.move_lines])
491 self.write(cr, uid, ids, {'state':'cancel'}) #,'move_lines':[(6,0,[])]})
494 #XXX: may be a bug here; lot_lines are unreserved for a few seconds;
495 # between the end of the picking list and the call to this function
496 def action_ready(self, cr, uid, ids):
497 self.write(cr, uid, ids, {'state':'ready'})
498 for production in self.browse(cr, uid, ids):
499 if production.move_prod_id:
500 self.pool.get('stock.move').write(cr, uid, [production.move_prod_id.id],
501 {'location_id':production.location_dest_id.id})
504 #TODO Review materials in function in_prod and prod_end.
505 def action_production_end(self, cr, uid, ids):
507 for production in self.browse(cr, uid, ids):
508 for res in production.move_lines:
509 for move in production.move_created_ids:
510 #XXX must use the orm
511 cr.execute('INSERT INTO stock_move_history_ids \
512 (parent_id, child_id) VALUES (%s,%s)',
514 # move_ids.append(res.id)
515 vals= {'state':'confirmed'}
516 new_moves = [x.id for x in production.move_created_ids if x.state not in ['done','cancel']]
517 self.pool.get('stock.move').write(cr, uid, new_moves, vals)
518 if not production.date_finnished:
519 self.write(cr, uid, [production.id],
520 {'date_finnished': time.strftime('%Y-%m-%d %H:%M:%S')})
521 self.pool.get('stock.move').check_assign(cr, uid, new_moves)
522 self.pool.get('stock.move').action_done(cr, uid, new_moves)
523 self._costs_generate(cr, uid, production)
524 # self.pool.get('stock.move').action_done(cr, uid, move_ids)
525 self.write(cr, uid, ids, {'state': 'done'})
528 def _costs_generate(self, cr, uid, production):
530 for wc_line in production.workcenter_lines:
531 wc = wc_line.workcenter_id
532 if wc.costs_journal_id and wc.costs_general_account_id:
533 value = wc_line.hour * wc.costs_hour
534 account = wc.costs_hour_account_id.id
535 if value and account:
537 self.pool.get('account.analytic.line').create(cr, uid, {
538 'name': wc_line.name+' (H)',
540 'account_id': account,
541 'general_account_id': wc.costs_general_account_id.id,
542 'journal_id': wc.costs_journal_id.id,
545 if wc.costs_journal_id and wc.costs_general_account_id:
546 value = wc_line.cycle * wc.costs_cycle
547 account = wc.costs_cycle_account_id.id
548 if value and account:
550 self.pool.get('account.analytic.line').create(cr, uid, {
551 'name': wc_line.name+' (C)',
553 'account_id': account,
554 'general_account_id': wc.costs_general_account_id.id,
555 'journal_id': wc.costs_journal_id.id,
560 def action_in_production(self, cr, uid, ids):
562 for production in self.browse(cr, uid, ids):
563 for res in production.move_lines:
564 move_ids.append(res.id)
565 if not production.date_start:
566 self.write(cr, uid, [production.id],
567 {'date_start': time.strftime('%Y-%m-%d %H:%M:%S')})
568 self.pool.get('stock.move').action_done(cr, uid, move_ids)
569 self.write(cr, uid, ids, {'state': 'in_production'})
572 def test_if_product(self, cr, uid, ids):
574 for production in self.browse(cr, uid, ids):
575 if not production.product_lines:
576 if not self.action_compute(cr, uid, [production.id]):
580 def _get_auto_picking(self, cr, uid, production):
583 def action_confirm(self, cr, uid, ids):
586 for production in self.browse(cr, uid, ids):
587 if not production.product_lines:
588 self.action_compute(cr, uid, [production.id])
589 production = self.browse(cr, uid, [production.id])[0]
591 pick_type = 'internal'
593 if production.bom_id.routing_id and production.bom_id.routing_id.location_id:
594 routing_loc = production.bom_id.routing_id.location_id
595 if routing_loc.usage<>'internal':
597 address_id = routing_loc.address_id and routing_loc.address_id.id or False
598 routing_loc = routing_loc.id
599 picking_id = self.pool.get('stock.picking').create(cr, uid, {
600 'origin': (production.origin or '').split(':')[0] +':'+production.name,
604 'address_id': address_id,
605 'auto_picking': self._get_auto_picking(cr, uid, production),
608 source = production.product_id.product_tmpl_id.property_stock_production.id
610 'name':'PROD:'+production.name,
611 'date_planned': production.date_planned,
612 'product_id': production.product_id.id,
613 'product_qty': production.product_qty,
614 'product_uom': production.product_uom.id,
615 'product_uos_qty': production.product_uos and production.product_uos_qty or False,
616 'product_uos': production.product_uos and production.product_uos.id or False,
617 'location_id': source,
618 'location_dest_id': production.location_dest_id.id,
619 'move_dest_id': production.move_prod_id.id,
622 res_final_id = self.pool.get('stock.move').create(cr, uid, data)
624 self.write(cr, uid, [production.id], {'move_created_ids': [(6, 0, [res_final_id])]})
626 for line in production.product_lines:
628 newdate = production.date_planned
629 if line.product_id.type in ('product', 'consu'):
630 res_dest_id = self.pool.get('stock.move').create(cr, uid, {
631 'name':'PROD:'+production.name,
632 'date_planned': production.date_planned,
633 'product_id': line.product_id.id,
634 'product_qty': line.product_qty,
635 'product_uom': line.product_uom.id,
636 'product_uos_qty': line.product_uos and line.product_uos_qty or False,
637 'product_uos': line.product_uos and line.product_uos.id or False,
638 'location_id': routing_loc or production.location_src_id.id,
639 'location_dest_id': source,
640 'move_dest_id': res_final_id,
643 moves.append(res_dest_id)
644 move_id = self.pool.get('stock.move').create(cr, uid, {
645 'name':'PROD:'+production.name,
646 'picking_id':picking_id,
647 'product_id': line.product_id.id,
648 'product_qty': line.product_qty,
649 'product_uom': line.product_uom.id,
650 'product_uos_qty': line.product_uos and line.product_uos_qty or False,
651 'product_uos': line.product_uos and line.product_uos.id or False,
652 'date_planned': newdate,
653 'move_dest_id': res_dest_id,
654 'location_id': production.location_src_id.id,
655 'location_dest_id': routing_loc or production.location_src_id.id,
658 proc_id = self.pool.get('mrp.procurement').create(cr, uid, {
659 'name': (production.origin or '').split(':')[0] + ':' + production.name,
660 'origin': (production.origin or '').split(':')[0] + ':' + production.name,
661 'date_planned': newdate,
662 'product_id': line.product_id.id,
663 'product_qty': line.product_qty,
664 'product_uom': line.product_uom.id,
665 'product_uos_qty': line.product_uos and line.product_qty or False,
666 'product_uos': line.product_uos and line.product_uos.id or False,
667 'location_id': production.location_src_id.id,
668 'procure_method': line.product_id.procure_method,
671 wf_service = netsvc.LocalService("workflow")
672 wf_service.trg_validate(uid, 'mrp.procurement', proc_id, 'button_confirm', cr)
673 proc_ids.append(proc_id)
674 wf_service = netsvc.LocalService("workflow")
675 wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
676 self.write(cr, uid, [production.id], {'picking_id':picking_id, 'move_lines': [(6,0,moves)], 'state':'confirmed'})
679 def force_production(self, cr, uid, ids, *args):
680 pick_obj = self.pool.get('stock.picking')
681 pick_obj.force_assign(cr, uid, [prod.picking_id.id for prod in self.browse(cr, uid, ids)])
687 class stock_move(osv.osv):
689 _inherit = 'stock.move'
691 'production_id': fields.many2one('mrp.production', 'Production', select=True),
695 class mrp_production_workcenter_line(osv.osv):
696 _name = 'mrp.production.workcenter.line'
697 _description = 'Work Orders'
700 'name': fields.char('Work Order', size=64, required=True),
701 'workcenter_id': fields.many2one('mrp.workcenter', 'Workcenter', required=True),
702 'cycle': fields.float('Nbr of cycle', digits=(16,2)),
703 'hour': fields.float('Nbr of hour', digits=(16,2)),
704 'sequence': fields.integer('Sequence', required=True),
705 'production_id': fields.many2one('mrp.production', 'Production Order', select=True, ondelete='cascade'),
708 'sequence': lambda *a: 1,
709 'hour': lambda *a: 0,
710 'cycle': lambda *a: 0,
712 mrp_production_workcenter_line()
714 class mrp_production_product_line(osv.osv):
715 _name = 'mrp.production.product.line'
716 _description = 'Production scheduled products'
718 'name': fields.char('Name', size=64, required=True),
719 'product_id': fields.many2one('product.product', 'Product', required=True),
720 'product_qty': fields.float('Product Qty', required=True),
721 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True),
722 'product_uos_qty': fields.float('Product UOS Qty'),
723 'product_uos': fields.many2one('product.uom', 'Product UOS'),
724 'production_id': fields.many2one('mrp.production', 'Production Order', select=True),
726 mrp_production_product_line()
728 # ------------------------------------------------------------------
730 # ------------------------------------------------------------------
732 # Produce, Buy or Find products and place a move
733 # then wizard for picking lists & move
735 class mrp_procurement(osv.osv):
736 _name = "mrp.procurement"
737 _description = "Procurement"
739 'name': fields.char('Name', size=64, required=True),
740 'origin': fields.char('Origin', size=64,
741 help="Reference of the document that created this procurement.\n"
742 "This is automatically completed by Open ERP."),
743 'priority': fields.selection([('0','Not urgent'),('1','Normal'),('2','Urgent'),('3','Very Urgent')], 'Priority', required=True),
744 'date_planned': fields.datetime('Scheduled date', required=True),
745 'date_close': fields.datetime('Date Closed'),
746 'product_id': fields.many2one('product.product', 'Product', required=True),
747 'product_qty': fields.float('Quantity', required=True, states={'draft':[('readonly',False)]}, readonly=True),
748 'product_uom': fields.many2one('product.uom', 'Product UoM', required=True, states={'draft':[('readonly',False)]}, readonly=True),
749 'product_uos_qty': fields.float('UoS Quantity', states={'draft':[('readonly',False)]}, readonly=True),
750 'product_uos': fields.many2one('product.uom', 'Product UoS', states={'draft':[('readonly',False)]}, readonly=True),
751 'move_id': fields.many2one('stock.move', 'Reservation', ondelete='set null'),
753 'bom_id': fields.many2one('mrp.bom', 'BoM', ondelete='cascade', select=True),
755 'close_move': fields.boolean('Close Move at end', required=True),
756 'location_id': fields.many2one('stock.location', 'Location', required=True),
757 'procure_method': fields.selection([('make_to_stock','from stock'),('make_to_order','on order')], 'Procurement Method', states={'draft':[('readonly',False)], 'confirmed':[('readonly',False)]},
758 readonly=True, required=True, help="If you encode manually a procurement, you probably want to use" \
759 " a make to order method."),
761 'purchase_id': fields.many2one('purchase.order', 'Purchase Order'),
762 'note': fields.text('Note'),
764 'property_ids': fields.many2many('mrp.property', 'mrp_procurement_property_rel', 'procurement_id','property_id', 'Properties'),
766 'message': fields.char('Latest error', size=64),
767 'state': fields.selection([
769 ('confirmed','Confirmed'),
770 ('exception','Exception'),
771 ('running','Running'),
775 ('waiting','Waiting')], 'Status', required=True),
778 'state': lambda *a: 'draft',
779 'priority': lambda *a: '1',
780 'date_planned': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
781 'close_move': lambda *a: 0,
782 'procure_method': lambda *a: 'make_to_order',
785 def unlink(self, cr, uid, ids, context=None):
786 procurements = self.read(cr, uid, ids, ['state'])
788 for s in procurements:
789 if s['state'] in ['draft','cancel']:
790 unlink_ids.append(s['id'])
792 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Procurement Order(s) which are in %s State!' % s['state']))
793 return osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
795 def onchange_product_id(self, cr, uid, ids, product_id, context={}):
797 w=self.pool.get('product.product').browse(cr,uid,product_id, context)
799 'product_uom':w.uom_id.id,
800 'product_uos':w.uos_id and w.uos_id.id or w.uom_id.id
805 def check_product(self, cr, uid, ids):
806 for procurement in self.browse(cr, uid, ids):
807 if procurement.product_id.type in ('product', 'consu'):
811 def get_phantom_bom_id(self, cr, uid, ids, context=None):
812 for procurement in self.browse(cr, uid, ids, context=context):
813 if procurement.move_id and procurement.move_id.product_id.supply_method=='produce' \
814 and procurement.move_id.product_id.procure_method=='make_to_order':
815 phantom_bom_id = self.pool.get('mrp.bom').search(cr, uid, [
816 ('product_id', '=', procurement.move_id.product_id.id),
817 ('bom_id', '=', False),
818 ('type', '=', 'phantom')])
819 return phantom_bom_id
822 def check_move_cancel(self, cr, uid, ids, context={}):
825 for procurement in self.browse(cr, uid, ids, context):
826 if procurement.move_id:
828 if not procurement.move_id.state=='cancel':
832 def check_move_done(self, cr, uid, ids, context={}):
834 for proc in self.browse(cr, uid, ids, context):
836 if not proc.move_id.state=='done':
841 # This method may be overrided by objects that override mrp.procurment
842 # for computing their own purpose
844 def _quantity_compute_get(self, cr, uid, proc, context={}):
845 if proc.product_id.type=='product':
846 if proc.move_id.product_uos:
847 return proc.move_id.product_uos_qty
850 def _uom_compute_get(self, cr, uid, proc, context={}):
851 if proc.product_id.type=='product':
852 if proc.move_id.product_uos:
853 return proc.move_id.product_uos.id
857 # Return the quantity of product shipped/produced/served, wich may be
858 # different from the planned quantity
860 def quantity_get(self, cr, uid, id, context={}):
861 proc = self.browse(cr, uid, id, context)
862 result = self._quantity_compute_get(cr, uid, proc, context)
864 result = proc.product_qty
867 def uom_get(self, cr, uid, id, context=None):
868 proc = self.browse(cr, uid, id, context)
869 result = self._uom_compute_get(cr, uid, proc, context)
871 result = proc.product_uom.id
874 def check_waiting(self, cr, uid, ids, context=[]):
875 for procurement in self.browse(cr, uid, ids, context=context):
876 if procurement.move_id and procurement.move_id.state=='auto':
880 def check_produce_service(self, cr, uid, procurement, context=[]):
883 def check_produce_product(self, cr, uid, procurement, context=[]):
884 properties = [x.id for x in procurement.property_ids]
885 bom_id = self.pool.get('mrp.bom')._bom_find(cr, uid, procurement.product_id.id, procurement.product_uom.id, properties)
887 cr.execute('update mrp_procurement set message=%s where id=%s', (_('No BoM defined for this product !'), procurement.id))
891 def check_make_to_stock(self, cr, uid, ids, context={}):
893 for procurement in self.browse(cr, uid, ids, context=context):
894 if procurement.product_id.type=='service':
895 ok = ok and self._check_make_to_stock_service(cr, uid, procurement, context)
897 ok = ok and self._check_make_to_stock_product(cr, uid, procurement, context)
900 def check_produce(self, cr, uid, ids, context={}):
902 user = self.pool.get('res.users').browse(cr, uid, uid)
903 for procurement in self.browse(cr, uid, ids):
904 if procurement.product_id.product_tmpl_id.supply_method<>'produce':
905 if procurement.product_id.seller_ids:
906 partner = procurement.product_id.seller_ids[0].name
907 if user.company_id and user.company_id.partner_id:
908 if partner.id == user.company_id.partner_id.id:
911 if procurement.product_id.product_tmpl_id.type=='service':
912 res = res and self.check_produce_service(cr, uid, procurement, context)
914 res = res and self.check_produce_product(cr, uid, procurement, context)
919 def check_buy(self, cr, uid, ids):
920 user = self.pool.get('res.users').browse(cr, uid, uid)
921 for procurement in self.browse(cr, uid, ids):
922 if procurement.product_id.product_tmpl_id.supply_method<>'buy':
924 if not procurement.product_id.seller_ids:
925 cr.execute('update mrp_procurement set message=%s where id=%s', (_('No supplier defined for this product !'), procurement.id))
927 partner = procurement.product_id.seller_ids[0].name
928 if user.company_id and user.company_id.partner_id:
929 if partner.id == user.company_id.partner_id.id:
931 address_id = self.pool.get('res.partner').address_get(cr, uid, [partner.id], ['delivery'])['delivery']
933 cr.execute('update mrp_procurement set message=%s where id=%s', (_('No address defined for the supplier'), procurement.id))
937 def test_cancel(self, cr, uid, ids):
938 for record in self.browse(cr, uid, ids):
939 if record.move_id and record.move_id.state=='cancel':
943 def action_confirm(self, cr, uid, ids, context={}):
944 for procurement in self.browse(cr, uid, ids):
945 if procurement.product_qty <= 0.00:
946 raise osv.except_osv(_('Data Insufficient !'), _('Please check the Quantity of Procurement Order(s), it should not be less than 1!'))
947 if procurement.product_id.type in ('product', 'consu'):
948 if not procurement.move_id:
949 source = procurement.location_id.id
950 if procurement.procure_method=='make_to_order':
951 source = procurement.product_id.product_tmpl_id.property_stock_procurement.id
952 id = self.pool.get('stock.move').create(cr, uid, {
953 'name': 'PROC:'+procurement.name,
954 'location_id': source,
955 'location_dest_id': procurement.location_id.id,
956 'product_id': procurement.product_id.id,
957 'product_qty':procurement.product_qty,
958 'product_uom': procurement.product_uom.id,
959 'date_planned': procurement.date_planned,
962 self.write(cr, uid, [procurement.id], {'move_id': id, 'close_move':1})
964 if procurement.procure_method=='make_to_stock' and procurement.move_id.state in ('draft','waiting',):
965 # properly call action_confirm() on stock.move to abide by the location chaining etc.
966 id = self.pool.get('stock.move').action_confirm(cr, uid, [procurement.move_id.id], context=context)
967 self.write(cr, uid, ids, {'state':'confirmed','message':''})
970 def action_move_assigned(self, cr, uid, ids, context={}):
971 self.write(cr, uid, ids, {'state':'running','message':_('from stock: products assigned.')})
974 def _check_make_to_stock_service(self, cr, uid, procurement, context={}):
977 def _check_make_to_stock_product(self, cr, uid, procurement, context={}):
979 if procurement.move_id:
980 id = procurement.move_id.id
981 if not (procurement.move_id.state in ('done','assigned','cancel')):
982 ok = ok and self.pool.get('stock.move').action_assign(cr, uid, [id])
983 cr.execute('select count(id) from stock_warehouse_orderpoint where product_id=%s', (procurement.product_id.id,))
984 if not cr.fetchone()[0]:
985 cr.execute('update mrp_procurement set message=%s where id=%s', (_('from stock and no minimum orderpoint rule defined'), procurement.id))
988 def action_produce_assign_service(self, cr, uid, ids, context={}):
989 for procurement in self.browse(cr, uid, ids):
990 self.write(cr, uid, [procurement.id], {'state':'running'})
993 def action_produce_assign_product(self, cr, uid, ids, context={}):
995 company = self.pool.get('res.users').browse(cr, uid, uid, context).company_id
996 move_obj = self.pool.get('stock.move')
997 for procurement in self.browse(cr, uid, ids):
998 res_id = procurement.move_id.id or False
1000 raise osv.except_osv(_('Warning !'), _('No reservation is defined for the selected procurement.'))
1001 move_obj.write(cr, uid, [res_id],{'location_id':procurement.location_id.id})
1002 loc_id = procurement.location_id.id
1003 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)
1004 newdate = newdate - DateTime.RelativeDateTime(days=company.manufacturing_lead)
1005 produce_id = self.pool.get('mrp.production').create(cr, uid, {
1006 'origin': procurement.origin,
1007 'product_id': procurement.product_id.id,
1008 'product_qty': procurement.product_qty,
1009 'product_uom': procurement.product_uom.id,
1010 'product_uos_qty': procurement.product_uos and procurement.product_uos_qty or False,
1011 'product_uos': procurement.product_uos and procurement.product_uos.id or False,
1012 'location_src_id': procurement.location_id.id,
1013 'location_dest_id': procurement.location_id.id,
1014 'bom_id': procurement.bom_id and procurement.bom_id.id or False,
1015 'date_planned': newdate.strftime('%Y-%m-%d %H:%M:%S'),
1016 'move_prod_id': res_id,
1019 self.write(cr, uid, [procurement.id], {'state':'running'})
1020 bom_result = self.pool.get('mrp.production').action_compute(cr, uid,
1021 [produce_id], properties=[x.id for x in procurement.property_ids])
1022 wf_service = netsvc.LocalService("workflow")
1023 wf_service.trg_validate(uid, 'mrp.production', produce_id, 'button_confirm', cr)
1026 def action_po_assign(self, cr, uid, ids, context={}):
1028 company = self.pool.get('res.users').browse(cr, uid, uid, context).company_id
1029 for procurement in self.browse(cr, uid, ids):
1030 res_id = procurement.move_id.id or False
1031 partner = procurement.product_id.seller_ids[0].name
1032 partner_id = partner.id
1033 address_id = self.pool.get('res.partner').address_get(cr, uid, [partner_id], ['delivery'])['delivery']
1034 pricelist_id = partner.property_product_pricelist_purchase.id
1036 uom_id = procurement.product_id.uom_po_id.id
1038 qty = self.pool.get('product.uom')._compute_qty(cr, uid, procurement.product_uom.id, procurement.product_qty, uom_id)
1039 if procurement.product_id.seller_ids[0].qty:
1040 qty=max(qty,procurement.product_id.seller_ids[0].qty)
1042 price = self.pool.get('product.pricelist').price_get(cr, uid, [pricelist_id], procurement.product_id.id, qty, partner_id, {'uom': uom_id})[pricelist_id]
1044 newdate = DateTime.strptime(procurement.date_planned, '%Y-%m-%d %H:%M:%S')
1045 newdate = newdate - DateTime.RelativeDateTime(days=company.po_lead)
1046 newdate = newdate - procurement.product_id.seller_ids[0].delay
1048 #Passing partner_id to context for purchase order line integrity of Line name
1049 context.update({'lang':partner.lang, 'partner_id':partner_id})
1051 product=self.pool.get('product.product').browse(cr,uid,procurement.product_id.id,context=context)
1054 'name': product.partner_ref,
1056 'product_id': procurement.product_id.id,
1057 'product_uom': uom_id,
1058 'price_unit': price,
1059 'date_planned': newdate.strftime('%Y-%m-%d %H:%M:%S'),
1060 'move_dest_id': res_id,
1061 'notes':product.description_purchase,
1064 taxes_ids = procurement.product_id.product_tmpl_id.supplier_taxes_id
1065 taxes = self.pool.get('account.fiscal.position').map_tax(cr, uid, partner.property_account_position, taxes_ids)
1067 'taxes_id':[(6,0,taxes)]
1069 purchase_id = self.pool.get('purchase.order').create(cr, uid, {
1070 'origin': procurement.origin,
1071 'partner_id': partner_id,
1072 'partner_address_id': address_id,
1073 'location_id': procurement.location_id.id,
1074 'pricelist_id': pricelist_id,
1075 'order_line': [(0,0,line)],
1076 'fiscal_position': partner.property_account_position and partner.property_account_position.id or False
1078 self.write(cr, uid, [procurement.id], {'state':'running', 'purchase_id':purchase_id})
1081 def action_cancel(self, cr, uid, ids):
1084 for proc in self.browse(cr, uid, ids):
1085 if proc.close_move and proc.move_id:
1086 if proc.move_id.state not in ('done','cancel'):
1087 todo2.append(proc.move_id.id)
1089 if proc.move_id and proc.move_id.state=='waiting':
1090 todo.append(proc.move_id.id)
1092 self.pool.get('stock.move').action_cancel(cr, uid, todo2)
1094 self.pool.get('stock.move').write(cr, uid, todo, {'state':'assigned'})
1095 self.write(cr, uid, ids, {'state':'cancel'})
1096 wf_service = netsvc.LocalService("workflow")
1098 wf_service.trg_trigger(uid, 'mrp.procurement', id, cr)
1101 def action_check_finnished(self, cr, uid, ids):
1102 return self.check_move_done(cr, uid, ids)
1104 def action_check(self, cr, uid, ids):
1106 for procurement in self.browse(cr, uid, ids):
1107 if procurement.move_id.state=='assigned' or procurement.move_id.state=='done':
1108 self.action_done(cr, uid, [procurement.id])
1112 def action_ready(self, cr, uid, ids):
1113 res = self.write(cr, uid, ids, {'state':'ready'})
1116 def action_done(self, cr, uid, ids):
1117 for procurement in self.browse(cr, uid, ids):
1118 if procurement.move_id:
1119 if procurement.close_move and (procurement.move_id.state <> 'done'):
1120 self.pool.get('stock.move').action_done(cr, uid, [procurement.move_id.id])
1121 res = self.write(cr, uid, ids, {'state':'done', 'date_close':time.strftime('%Y-%m-%d')})
1122 wf_service = netsvc.LocalService("workflow")
1124 wf_service.trg_trigger(uid, 'mrp.procurement', id, cr)
1127 def run_scheduler(self, cr, uid, automatic=False, use_new_cursor=False, context=None):
1129 use_new_cursor: False or the dbname
1133 self._procure_confirm(cr, uid, use_new_cursor=use_new_cursor, context=context)
1134 self._procure_orderpoint_confirm(cr, uid, automatic=automatic,\
1135 use_new_cursor=use_new_cursor, context=context)
1139 class stock_warehouse_orderpoint(osv.osv):
1140 _name = "stock.warehouse.orderpoint"
1141 _description = "Orderpoint minimum rule"
1143 'name': fields.char('Name', size=32, required=True),
1144 'active': fields.boolean('Active'),
1145 'logic': fields.selection([('max','Order to Max'),('price','Best price (not yet active!)')], 'Reordering Mode', required=True),
1146 'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse', required=True, ondelete="cascade"),
1147 'location_id': fields.many2one('stock.location', 'Location', required=True, ondelete="cascade"),
1148 'product_id': fields.many2one('product.product', 'Product', required=True, domain=[('type','=','product')], ondelete="cascade"),
1149 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True),
1150 'product_min_qty': fields.float('Min Quantity', required=True,
1151 help="When the virtual stock goes belong the Min Quantity, Open ERP generates "\
1152 "a procurement to bring the virtual stock to the Max Quantity."),
1153 'product_max_qty': fields.float('Max Quantity', required=True,
1154 help="When the virtual stock goes belong the Min Quantity, Open ERP generates "\
1155 "a procurement to bring the virtual stock to the Max Quantity."),
1156 'qty_multiple': fields.integer('Qty Multiple', required=True,
1157 help="The procurement quantity will by rounded up to this multiple."),
1158 'procurement_id': fields.many2one('mrp.procurement', 'Purchase Order', ondelete="set null")
1161 'active': lambda *a: 1,
1162 'logic': lambda *a: 'max',
1163 'qty_multiple': lambda *a: 1,
1164 'name': lambda x,y,z,c: x.pool.get('ir.sequence').get(y,z,'mrp.warehouse.orderpoint') or '',
1165 'product_uom': lambda sel, cr, uid, context: context.get('product_uom', False),
1168 _sql_constraints = [
1169 ( 'qty_multiple_check', 'CHECK( qty_multiple > 0 )', _('Qty Multiple must be greater than zero.')),
1172 def onchange_warehouse_id(self, cr, uid, ids, warehouse_id, context={}):
1174 w=self.pool.get('stock.warehouse').browse(cr,uid,warehouse_id, context)
1175 v = {'location_id':w.lot_stock_id.id}
1178 def onchange_product_id(self, cr, uid, ids, product_id, context={}):
1180 prod=self.pool.get('product.product').browse(cr,uid,product_id)
1181 v = {'product_uom':prod.uom_id.id}
1184 def copy(self, cr, uid, id, default=None,context={}):
1188 'name': self.pool.get('ir.sequence').get(cr, uid, 'mrp.warehouse.orderpoint') or '',
1190 return super(stock_warehouse_orderpoint, self).copy(cr, uid, id, default, context)
1191 stock_warehouse_orderpoint()
1194 class StockMove(osv.osv):
1195 _inherit = 'stock.move'
1197 'procurements': fields.one2many('mrp.procurement', 'move_id', 'Procurements'),
1199 def copy(self, cr, uid, id, default=None, context=None):
1200 default = default or {}
1201 default['procurements'] = []
1202 return super(StockMove, self).copy(cr, uid, id, default, context)
1204 def _action_explode(self, cr, uid, move, context={}):
1205 if move.product_id.supply_method=='produce' and move.product_id.procure_method=='make_to_order':
1206 bis = self.pool.get('mrp.bom').search(cr, uid, [
1207 ('product_id','=',move.product_id.id),
1208 ('bom_id','=',False),
1209 ('type','=','phantom')])
1211 factor = move.product_qty
1212 bom_point = self.pool.get('mrp.bom').browse(cr, uid, bis[0])
1213 res = self.pool.get('mrp.bom')._bom_explode(cr, uid, bom_point, factor, [])
1215 if move.state=='assigned':
1219 'picking_id': move.picking_id.id,
1220 'product_id': line['product_id'],
1221 'product_uom': line['product_uom'],
1222 'product_qty': line['product_qty'],
1223 'product_uos': line['product_uos'],
1224 'product_uos_qty': line['product_uos_qty'],
1225 'move_dest_id': move.id,
1227 'name': line['name'],
1228 'move_history_ids': [(6,0,[move.id])],
1229 'move_history_ids2': [(6,0,[])],
1232 mid = self.pool.get('stock.move').copy(cr, uid, move.id, default=valdef)
1233 prodobj = self.pool.get('product.product').browse(cr, uid, line['product_id'], context=context)
1234 proc_id = self.pool.get('mrp.procurement').create(cr, uid, {
1235 'name': (move.picking_id.origin or ''),
1236 'origin': (move.picking_id.origin or ''),
1237 'date_planned': move.date_planned,
1238 'product_id': line['product_id'],
1239 'product_qty': line['product_qty'],
1240 'product_uom': line['product_uom'],
1241 'product_uos_qty': line['product_uos'] and line['product_uos_qty'] or False,
1242 'product_uos': line['product_uos'],
1243 'location_id': move.location_id.id,
1244 'procure_method': prodobj.procure_method,
1247 wf_service = netsvc.LocalService("workflow")
1248 wf_service.trg_validate(uid, 'mrp.procurement', proc_id, 'button_confirm', cr)
1249 self.pool.get('stock.move').write(cr, uid, [move.id], {
1250 'location_id': move.location_dest_id.id, # src and dest locations identical to have correct inventory, dummy move
1251 'auto_validate': True,
1252 'picking_id': False,
1255 for m in self.pool.get('mrp.procurement').search(cr, uid, [('move_id','=',move.id)], context):
1256 wf_service = netsvc.LocalService("workflow")
1257 wf_service.trg_validate(uid, 'mrp.procurement', m, 'button_wait_done', cr)
1262 class StockPicking(osv.osv):
1263 _inherit = 'stock.picking'
1265 def test_finnished(self, cursor, user, ids):
1266 wf_service = netsvc.LocalService("workflow")
1267 res = super(StockPicking, self).test_finnished(cursor, user, ids)
1268 for picking in self.browse(cursor, user, ids):
1269 for move in picking.move_lines:
1270 if move.state == 'done' and move.procurements:
1271 for procurement in move.procurements:
1272 wf_service.trg_validate(user, 'mrp.procurement',
1273 procurement.id, 'button_check', cursor)
1277 # Explode picking by replacing phantom BoMs
1279 def action_explode(self, cr, uid, picks, *args):
1280 for move in self.pool.get('stock.move').browse(cr, uid, picks):
1281 self.pool.get('stock.move')._action_explode(cr, uid, move)
1286 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: