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 = 'Work Center'
44 'name': fields.char('Work Center Name', size=64, required=True),
45 'active': fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the work center without removing it."),
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 operations this workcenter can do in parallel. If this workcenter represents 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('Efficiency factor', 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')]),
66 'company_id': fields.many2one('res.company','Company',required=True),
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,
73 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'mrp.workcenter', c)
78 class mrp_property_group(osv.osv):
79 _name = 'mrp.property.group'
80 _description = 'Property Group'
82 'name': fields.char('Property Group', size=64, required=True),
83 'description': fields.text('Description'),
87 class mrp_property(osv.osv):
88 _name = 'mrp.property'
89 _description = 'Property'
91 'name': fields.char('Name', size=64, required=True),
92 'composition': fields.selection([('min','min'),('max','max'),('plus','plus')], 'Properties composition', required=True, help="Not used in computations, for information purpose only."),
93 'group_id': fields.many2one('mrp.property.group', 'Property Group', required=True),
94 'description': fields.text('Description'),
97 'composition': lambda *a: 'min',
101 class mrp_routing(osv.osv):
102 _name = 'mrp.routing'
103 _description = 'Routing'
105 'name': fields.char('Name', size=64, required=True),
106 'active': fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the routing without removing it."),
107 'code': fields.char('Code', size=8),
109 'note': fields.text('Description'),
110 'workcenter_lines': fields.one2many('mrp.routing.workcenter', 'routing_id', 'Work Centers'),
112 'location_id': fields.many2one('stock.location', 'Production Location',
113 help="Keep empty if you produce at the location where the finished products are needed." \
114 "Set a location if you produce at a fixed location. This can be a partner location " \
115 "if you subcontract the manufacturing operations."
119 'active': lambda *a: 1,
123 class mrp_routing_workcenter(osv.osv):
124 _name = 'mrp.routing.workcenter'
125 _description = 'Routing workcenter usage'
127 'workcenter_id': fields.many2one('mrp.workcenter', 'Work Center', required=True),
128 'name': fields.char('Name', size=64, required=True),
129 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of routing workcenters."),
130 'cycle_nbr': fields.float('Number of Cycles', required=True,
131 help="Time in hours for doing one cycle."),
132 'hour_nbr': fields.float('Number of Hours', required=True, help="Cost per hour"),
133 'routing_id': fields.many2one('mrp.routing', 'Parent Routing', select=True, ondelete='cascade',
134 help="Routing indicates all the workcenters used, for how long and/or cycles." \
135 "If Routing is indicated then,the third tab of a production order (workcenters) will be automatically pre-completed."),
136 'note': fields.text('Description')
139 'cycle_nbr': lambda *a: 1.0,
140 'hour_nbr': lambda *a: 0.0,
142 mrp_routing_workcenter()
144 class mrp_bom(osv.osv):
146 _description = 'Bills of Material'
147 def _child_compute(self, cr, uid, ids, name, arg, context={}):
149 for bom in self.browse(cr, uid, ids, context=context):
150 result[bom.id] = map(lambda x: x.id, bom.bom_lines)
153 ok = ((name=='child_complete_ids') and (bom.product_id.supply_method=='produce'))
154 if bom.type=='phantom' or ok:
155 sids = self.pool.get('mrp.bom').search(cr, uid, [('bom_id','=',False),('product_id','=',bom.product_id.id)])
157 bom2 = self.pool.get('mrp.bom').browse(cr, uid, sids[0], context=context)
158 result[bom.id] += map(lambda x: x.id, bom2.bom_lines)
160 def _compute_type(self, cr, uid, ids, field_name, arg, context):
161 res = dict(map(lambda x: (x,''), ids))
162 for line in self.browse(cr, uid, ids):
163 if line.type=='phantom' and not line.bom_id:
166 if line.bom_lines or line.type=='phantom':
168 if line.product_id.supply_method=='produce':
169 if line.product_id.procure_method=='make_to_stock':
170 res[line.id] = 'stock'
172 res[line.id] = 'order'
175 'name': fields.char('Name', size=64, required=True),
176 'code': fields.char('Code', size=16),
177 'active': fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the bills of material without removing it."),
178 'type': fields.selection([('normal','Normal BoM'),('phantom','Sets / Phantom')], 'BoM Type', required=True, help=
179 "Use a phantom bill of material in raw materials lines that have to be " \
180 "automatically computed in on eproduction order and not one per level." \
181 "If you put \"Phantom/Set\" at the root level of a bill of material " \
182 "it is considered as a set or pack: the products are replaced by the components " \
183 "between the sale order to the picking without going through the production order." \
184 "The normal BoM will generate one production order per BoM level."),
185 'method': fields.function(_compute_type, string='Method', method=True, type='selection', selection=[('',''),('stock','On Stock'),('order','On Order'),('set','Set / Pack')]),
186 'date_start': fields.date('Valid From', help="Validity of this BoM or component. Keep empty if it's always valid."),
187 'date_stop': fields.date('Valid Until', help="Validity of this BoM or component. Keep empty if it's always valid."),
188 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of bills of material."),
189 'position': fields.char('Internal Reference', size=64, help="Reference to a position in an external plan."),
190 'product_id': fields.many2one('product.product', 'Product', required=True),
191 'product_uos_qty': fields.float('Product UOS Qty'),
192 '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."),
193 'product_qty': fields.float('Product Qty', required=True),
194 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True, help="UoM (Unit of Measure) is the unit of measurement for the inventory control"),
195 'product_rounding': fields.float('Product Rounding', help="Rounding applied on the product quantity. For integer only values, put 1.0"),
196 'product_efficiency': fields.float('Product Efficiency', required=True, help="Material efficiency. A factor of 0.9 means a loss of 10% in the production."),
197 'bom_lines': fields.one2many('mrp.bom', 'bom_id', 'BoM Lines'),
198 'bom_id': fields.many2one('mrp.bom', 'Parent BoM', ondelete='cascade', select=True),
199 '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 future loads on workcenters based on production planning."),
200 'property_ids': fields.many2many('mrp.property', 'mrp_bom_property_rel', 'bom_id','property_id', 'Properties'),
201 'revision_ids': fields.one2many('mrp.bom.revision', 'bom_id', 'BoM Revisions'),
202 'revision_type': fields.selection([('numeric','numeric indices'),('alpha','alphabetical indices')], 'Index type'),
203 'child_ids': fields.function(_child_compute,relation='mrp.bom', method=True, string="BoM Hierarchy", type='many2many'),
204 'child_complete_ids': fields.function(_child_compute,relation='mrp.bom', method=True, string="BoM Hierarchy", type='many2many'),
205 'company_id': fields.many2one('res.company','Company',required=True),
208 'active': lambda *a: 1,
209 'product_efficiency': lambda *a: 1.0,
210 'product_qty': lambda *a: 1.0,
211 'product_rounding': lambda *a: 1.0,
212 'type': lambda *a: 'normal',
213 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'mrp.bom', c)
217 ('bom_qty_zero', 'CHECK (product_qty>0)', 'All product quantities must be greater than 0.\n' \
218 'You should install the mrp_subproduct module if you want to manage extra products on BoMs !'),
221 def _check_recursion(self, cr, uid, ids):
224 cr.execute('select distinct bom_id from mrp_bom where id in ('+','.join(map(str, ids))+')')
225 ids = filter(None, map(lambda x:x[0], cr.fetchall()))
231 (_check_recursion, 'Error ! You can not create recursive BoM.', ['parent_id'])
235 def onchange_product_id(self, cr, uid, ids, product_id, name, context={}):
237 prod=self.pool.get('product.product').browse(cr,uid,[product_id])[0]
238 v = {'product_uom':prod.uom_id.id}
240 v['name'] = prod.name
244 def _bom_find(self, cr, uid, product_id, product_uom, properties=[]):
246 # Why searching on BoM without parent ?
247 cr.execute('select id from mrp_bom where product_id=%s and bom_id is null order by sequence', (product_id,))
248 ids = map(lambda x: x[0], cr.fetchall())
251 for bom in self.pool.get('mrp.bom').browse(cr, uid, ids):
253 for prop_id in bom.property_ids:
254 if prop_id.id in properties:
256 if (prop>max_prop) or ((max_prop==0) and not result):
261 def _bom_explode(self, cr, uid, bom, factor, properties, addthis=False, level=0):
262 factor = factor / (bom.product_efficiency or 1.0)
263 factor = rounding(factor, bom.product_rounding)
264 if factor<bom.product_rounding:
265 factor = bom.product_rounding
269 if bom.type=='phantom' and not bom.bom_lines:
270 newbom = self._bom_find(cr, uid, bom.product_id.id, bom.product_uom.id, properties)
272 res = self._bom_explode(cr, uid, self.browse(cr, uid, [newbom])[0], factor*bom.product_qty, properties, addthis=True, level=level+10)
273 result = result + res[0]
274 result2 = result2 + res[1]
279 if addthis and not bom.bom_lines:
282 'name': bom.product_id.name,
283 'product_id': bom.product_id.id,
284 'product_qty': bom.product_qty * factor,
285 'product_uom': bom.product_uom.id,
286 'product_uos_qty': bom.product_uos and bom.product_uos_qty * factor or False,
287 'product_uos': bom.product_uos and bom.product_uos.id or False,
290 for wc_use in bom.routing_id.workcenter_lines:
291 wc = wc_use.workcenter_id
292 d, m = divmod(factor, wc_use.workcenter_id.capacity_per_cycle)
293 mult = (d + (m and 1.0 or 0.0))
294 cycle = mult * wc_use.cycle_nbr
296 'name': bom.routing_id.name,
297 'workcenter_id': wc.id,
298 'sequence': level+(wc_use.sequence or 0),
300 'hour': float(wc_use.hour_nbr*mult + (wc.time_start+wc.time_stop+cycle*wc.time_cycle) * (wc.time_efficiency or 1.0)),
302 for bom2 in bom.bom_lines:
303 res = self._bom_explode(cr, uid, bom2, factor, properties, addthis=True, level=level+10)
304 result = result + res[0]
305 result2 = result2 + res[1]
306 return result, result2
308 def set_indices(self, cr, uid, ids, context = {}):
309 if not ids or (ids and not ids[0]):
311 res = self.read(cr, uid, ids, ['revision_ids', 'revision_type'])
312 rev_ids = res[0]['revision_ids']
315 for rev_id in rev_ids:
316 if res[0]['revision_type'] == 'numeric':
317 self.pool.get('mrp.bom.revision').write(cr, uid, [rev_id], {'indice' : idx})
319 self.pool.get('mrp.bom.revision').write(cr, uid, [rev_id], {'indice' : "%c"%(idx+96,)})
325 class mrp_bom_revision(osv.osv):
326 _name = 'mrp.bom.revision'
327 _description = 'Bill of material revisions'
329 'name': fields.char('Modification name', size=64, required=True),
330 'description': fields.text('Description'),
331 'date': fields.date('Modification Date'),
332 'indice': fields.char('Revision', size=16),
333 'last_indice': fields.char('last indice', size=64),
334 'author_id': fields.many2one('res.users', 'Author'),
335 'bom_id': fields.many2one('mrp.bom', 'BoM', select=True),
339 'author_id': lambda x,y,z,c: z,
340 'date': lambda *a: time.strftime('%Y-%m-%d'),
348 return round(f / r) * r
350 class mrp_production(osv.osv):
351 _name = 'mrp.production'
352 _description = 'Production'
353 _date_name = 'date_planned'
355 def _get_sale_order(self,cr,uid,ids,field_name=False):
356 move_obj=self.pool.get('stock.move')
357 def get_parent_move(move_id):
358 move = move_obj.browse(cr,uid,move_id)
359 if move.move_dest_id:
360 return get_parent_move(move.move_dest_id.id)
362 productions=self.read(cr,uid,ids,['id','move_prod_id'])
364 for production in productions:
365 res[production['id']]=False
366 if production.get('move_prod_id',False):
367 parent_move_line=get_parent_move(production['move_prod_id'][0])
369 move = move_obj.browse(cr,uid,parent_move_line)
370 #TODO: fix me sale module can not be used here,
371 #as may be mrp can be installed without sale module
372 if field_name=='name':
373 res[production['id']]=move.sale_line_id and move.sale_line_id.order_id.name or False
374 if field_name=='client_order_ref':
375 res[production['id']]=move.sale_line_id and move.sale_line_id.order_id.client_order_ref or False
378 def _production_calc(self, cr, uid, ids, prop, unknow_none, context={}):
380 for prod in self.browse(cr, uid, ids, context=context):
385 for wc in prod.workcenter_lines:
386 result[prod.id]['hour_total'] += wc.hour
387 result[prod.id]['cycle_total'] += wc.cycle
390 def _production_date_end(self, cr, uid, ids, prop, unknow_none, context={}):
392 for prod in self.browse(cr, uid, ids, context=context):
393 result[prod.id] = prod.date_planned
396 def _production_date(self, cr, uid, ids, prop, unknow_none, context={}):
398 for prod in self.browse(cr, uid, ids, context=context):
399 result[prod.id] = prod.date_planned[:10]
402 def _sale_name_calc(self, cr, uid, ids, prop, unknow_none, unknow_dict):
403 return self._get_sale_order(cr,uid,ids,field_name='name')
405 def _sale_ref_calc(self, cr, uid, ids, prop, unknow_none, unknow_dict):
406 return self._get_sale_order(cr,uid,ids,field_name='client_order_ref')
409 'name': fields.char('Reference', size=64, required=True),
410 'origin': fields.char('Origin', size=64, help="Reference of the document that generated this production order request."),
411 'priority': fields.selection([('0','Not urgent'),('1','Normal'),('2','Urgent'),('3','Very Urgent')], 'Priority'),
413 'product_id': fields.many2one('product.product', 'Product', required=True, domain=[('type','<>','service')]),
414 'product_qty': fields.float('Product Qty', required=True, states={'draft':[('readonly',False)]}, readonly=True),
415 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True, states={'draft':[('readonly',False)]}, readonly=True),
416 'product_uos_qty': fields.float('Product UoS Qty', states={'draft':[('readonly',False)]}, readonly=True),
417 'product_uos': fields.many2one('product.uom', 'Product UoS', states={'draft':[('readonly',False)]}, readonly=True),
419 'location_src_id': fields.many2one('stock.location', 'Raw Materials Location', required=True,
420 help="Location where the system will look for components."),
421 'location_dest_id': fields.many2one('stock.location', 'Finished Products Location', required=True,
422 help="Location where the system will stock the finished products."),
424 'date_planned_end': fields.function(_production_date_end, method=True, type='date', string='Scheduled End'),
425 'date_planned_date': fields.function(_production_date, method=True, type='date', string='Scheduled Date'),
426 'date_planned': fields.datetime('Scheduled date', required=True, select=1),
427 'date_start': fields.datetime('Start Date'),
428 'date_finnished': fields.datetime('End Date'),
430 'bom_id': fields.many2one('mrp.bom', 'Bill of Material', domain=[('bom_id','=',False)]),
431 '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."),
433 'picking_id': fields.many2one('stock.picking', 'Picking list', readonly=True,
434 help="This is the internal picking list that brings the finished product to the production plan"),
435 'move_prod_id': fields.many2one('stock.move', 'Move product', readonly=True),
436 'move_lines': fields.many2many('stock.move', 'mrp_production_move_ids', 'production_id', 'move_id', 'Products Consummed'),
438 'move_created_ids': fields.one2many('stock.move', 'production_id', 'Moves Created'),
439 'product_lines': fields.one2many('mrp.production.product.line', 'production_id', 'Scheduled goods'),
440 'workcenter_lines': fields.one2many('mrp.production.workcenter.line', 'production_id', 'Work Centers Utilisation'),
441 '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,
442 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\'.\
443 \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\'.'),
444 'hour_total': fields.function(_production_calc, method=True, type='float', string='Total Hours', multi='workorder'),
445 'cycle_total': fields.function(_production_calc, method=True, type='float', string='Total Cycles', multi='workorder'),
447 'sale_name': fields.function(_sale_name_calc, method=True, type='char', string='Sale Name'),
448 'sale_ref': fields.function(_sale_ref_calc, method=True, type='char', string='Sale Reference'),
449 'company_id': fields.many2one('res.company','Company',required=True),
452 'priority': lambda *a: '1',
453 'state': lambda *a: 'draft',
454 'date_planned': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
455 'product_qty': lambda *a: 1.0,
456 'name': lambda x,y,z,c: x.pool.get('ir.sequence').get(y,z,'mrp.production') or '/',
457 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'mrp.production', c),
459 _order = 'date_planned asc, priority desc';
460 def unlink(self, cr, uid, ids, context=None):
461 productions = self.read(cr, uid, ids, ['state'])
463 for s in productions:
464 if s['state'] in ['draft','cancel']:
465 unlink_ids.append(s['id'])
467 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Production Order(s) which are in %s State!' % s['state']))
468 return osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
470 def copy(self, cr, uid, id, default=None,context=None):
474 'name': self.pool.get('ir.sequence').get(cr, uid, 'mrp.production'),
476 'move_created_ids': [],
479 return super(mrp_production, self).copy(cr, uid, id, default, context)
481 def location_id_change(self, cr, uid, ids, src, dest, context={}):
485 return {'value': {'location_dest_id': src}}
488 def product_id_change(self, cr, uid, ids, product):
491 res = self.pool.get('product.product').read(cr, uid, [product], ['uom_id'])[0]
492 uom = res['uom_id'] and res['uom_id'][0]
493 result = {'product_uom':uom}
494 return {'value':result}
496 def bom_id_change(self, cr, uid, ids, product):
499 res = self.pool.get('mrp.bom').read(cr, uid, [product], ['routing_id'])[0]
500 routing_id = res['routing_id'] and res['routing_id'][0]
501 result = {'routing_id':routing_id}
502 return {'value':result}
504 def action_picking_except(self, cr, uid, ids):
505 self.write(cr, uid, ids, {'state':'picking_except'})
508 def action_compute(self, cr, uid, ids, properties=[]):
510 for production in self.browse(cr, uid, ids):
511 cr.execute('delete from mrp_production_product_line where production_id=%s', (production.id,))
512 cr.execute('delete from mrp_production_workcenter_line where production_id=%s', (production.id,))
513 bom_point = production.bom_id
514 bom_id = production.bom_id.id
516 bom_id = self.pool.get('mrp.bom')._bom_find(cr, uid, production.product_id.id, production.product_uom.id, properties)
518 bom_point = self.pool.get('mrp.bom').browse(cr, uid, bom_id)
519 routing_id = bom_point.routing_id.id or False
520 self.write(cr, uid, [production.id], {'bom_id': bom_id, 'routing_id': routing_id})
523 raise osv.except_osv(_('Error'), _("Couldn't find bill of material for product"))
525 #if bom_point.routing_id and bom_point.routing_id.location_id:
526 # self.write(cr, uid, [production.id], {'location_src_id': bom_point.routing_id.location_id.id})
528 factor = production.product_qty * production.product_uom.factor / bom_point.product_uom.factor
529 res = self.pool.get('mrp.bom')._bom_explode(cr, uid, bom_point, factor / bom_point.product_qty, properties)
533 line['production_id'] = production.id
534 self.pool.get('mrp.production.product.line').create(cr, uid, line)
535 for line in results2:
536 line['production_id'] = production.id
537 self.pool.get('mrp.production.workcenter.line').create(cr, uid, line)
540 def action_cancel(self, cr, uid, ids):
541 for production in self.browse(cr, uid, ids):
542 if production.move_created_ids:
543 self.pool.get('stock.move').action_cancel(cr, uid, [x.id for x in production.move_created_ids])
544 self.pool.get('stock.move').action_cancel(cr, uid, [x.id for x in production.move_lines])
545 self.write(cr, uid, ids, {'state':'cancel'}) #,'move_lines':[(6,0,[])]})
548 #XXX: may be a bug here; lot_lines are unreserved for a few seconds;
549 # between the end of the picking list and the call to this function
550 def action_ready(self, cr, uid, ids):
551 self.write(cr, uid, ids, {'state':'ready'})
552 for production in self.browse(cr, uid, ids):
553 if production.move_prod_id:
554 self.pool.get('stock.move').write(cr, uid, [production.move_prod_id.id],
555 {'location_id':production.location_dest_id.id})
558 #TODO Review materials in function in_prod and prod_end.
559 def action_production_end(self, cr, uid, ids):
561 for production in self.browse(cr, uid, ids):
562 for res in production.move_lines:
563 for move in production.move_created_ids:
564 #XXX must use the orm
565 cr.execute('INSERT INTO stock_move_history_ids \
566 (parent_id, child_id) VALUES (%s,%s)',
568 move_ids.append(res.id)
569 vals= {'state':'confirmed'}
570 new_moves = [x.id for x in production.move_created_ids]
571 self.pool.get('stock.move').write(cr, uid, new_moves, vals)
572 if not production.date_finnished:
573 self.write(cr, uid, [production.id],
574 {'date_finnished': time.strftime('%Y-%m-%d %H:%M:%S')})
575 self.pool.get('stock.move').check_assign(cr, uid, new_moves)
576 self.pool.get('stock.move').action_done(cr, uid, new_moves)
577 self._costs_generate(cr, uid, production)
578 self.pool.get('stock.move').action_done(cr, uid, move_ids)
579 self.write(cr, uid, ids, {'state': 'done'})
582 def _costs_generate(self, cr, uid, production):
584 for wc_line in production.workcenter_lines:
585 wc = wc_line.workcenter_id
586 if wc.costs_journal_id and wc.costs_general_account_id:
587 value = wc_line.hour * wc.costs_hour
588 account = wc.costs_hour_account_id.id
589 if value and account:
591 self.pool.get('account.analytic.line').create(cr, uid, {
592 'name': wc_line.name+' (H)',
594 'account_id': account,
595 'general_account_id': wc.costs_general_account_id.id,
596 'journal_id': wc.costs_journal_id.id,
599 if wc.costs_journal_id and wc.costs_general_account_id:
600 value = wc_line.cycle * wc.costs_cycle
601 account = wc.costs_cycle_account_id.id
602 if value and account:
604 self.pool.get('account.analytic.line').create(cr, uid, {
605 'name': wc_line.name+' (C)',
607 'account_id': account,
608 'general_account_id': wc.costs_general_account_id.id,
609 'journal_id': wc.costs_journal_id.id,
614 def action_in_production(self, cr, uid, ids):
616 for production in self.browse(cr, uid, ids):
617 for res in production.move_lines:
618 move_ids.append(res.id)
619 if not production.date_start:
620 self.write(cr, uid, [production.id],
621 {'date_start': time.strftime('%Y-%m-%d %H:%M:%S')})
622 self.pool.get('stock.move').action_done(cr, uid, move_ids)
623 self.write(cr, uid, ids, {'state': 'in_production'})
626 def test_if_product(self, cr, uid, ids):
628 for production in self.browse(cr, uid, ids):
629 if not production.product_lines:
630 if not self.action_compute(cr, uid, [production.id]):
634 def _get_auto_picking(self, cr, uid, production):
637 def action_confirm(self, cr, uid, ids):
640 for production in self.browse(cr, uid, ids):
641 if not production.product_lines:
642 self.action_compute(cr, uid, [production.id])
643 production = self.browse(cr, uid, [production.id])[0]
645 pick_type = 'internal'
647 if production.bom_id.routing_id and production.bom_id.routing_id.location_id:
648 routing_loc = production.bom_id.routing_id.location_id
649 if routing_loc.usage<>'internal':
651 address_id = routing_loc.address_id and routing_loc.address_id.id or False
652 routing_loc = routing_loc.id
653 picking_id = self.pool.get('stock.picking').create(cr, uid, {
654 'origin': (production.origin or '').split(':')[0] +':'+production.name,
658 'address_id': address_id,
659 'auto_picking': self._get_auto_picking(cr, uid, production),
660 'company_id': production.company_id.id,
663 source = production.product_id.product_tmpl_id.property_stock_production.id
665 'name':'PROD:'+production.name,
666 'date_planned': production.date_planned,
667 'product_id': production.product_id.id,
668 'product_qty': production.product_qty,
669 'product_uom': production.product_uom.id,
670 'product_uos_qty': production.product_uos and production.product_uos_qty or False,
671 'product_uos': production.product_uos and production.product_uos.id or False,
672 'location_id': source,
673 'location_dest_id': production.location_dest_id.id,
674 'move_dest_id': production.move_prod_id.id,
676 'company_id': production.company_id.id,
678 res_final_id = self.pool.get('stock.move').create(cr, uid, data)
680 self.write(cr, uid, [production.id], {'move_created_ids': [(6, 0, [res_final_id])]})
682 for line in production.product_lines:
684 newdate = production.date_planned
685 if line.product_id.type in ('product', 'consu'):
686 res_dest_id = self.pool.get('stock.move').create(cr, uid, {
687 'name':'PROD:'+production.name,
688 'date_planned': production.date_planned,
689 'product_id': line.product_id.id,
690 'product_qty': line.product_qty,
691 'product_uom': line.product_uom.id,
692 'product_uos_qty': line.product_uos and line.product_uos_qty or False,
693 'product_uos': line.product_uos and line.product_uos.id or False,
694 'location_id': routing_loc or production.location_src_id.id,
695 'location_dest_id': source,
696 'move_dest_id': res_final_id,
698 'company_id': production.company_id.id,
700 moves.append(res_dest_id)
701 move_id = self.pool.get('stock.move').create(cr, uid, {
702 'name':'PROD:'+production.name,
703 'picking_id':picking_id,
704 'product_id': line.product_id.id,
705 'product_qty': line.product_qty,
706 'product_uom': line.product_uom.id,
707 'product_uos_qty': line.product_uos and line.product_uos_qty or False,
708 'product_uos': line.product_uos and line.product_uos.id or False,
709 'date_planned': newdate,
710 'move_dest_id': res_dest_id,
711 'location_id': production.location_src_id.id,
712 'location_dest_id': routing_loc or production.location_src_id.id,
714 'company_id': production.company_id.id,
716 proc_id = self.pool.get('mrp.procurement').create(cr, uid, {
717 'name': (production.origin or '').split(':')[0] + ':' + production.name,
718 'origin': (production.origin or '').split(':')[0] + ':' + production.name,
719 'date_planned': newdate,
720 'product_id': line.product_id.id,
721 'product_qty': line.product_qty,
722 'product_uom': line.product_uom.id,
723 'product_uos_qty': line.product_uos and line.product_qty or False,
724 'product_uos': line.product_uos and line.product_uos.id or False,
725 'location_id': production.location_src_id.id,
726 'procure_method': line.product_id.procure_method,
728 'company_id': production.company_id.id,
730 wf_service = netsvc.LocalService("workflow")
731 wf_service.trg_validate(uid, 'mrp.procurement', proc_id, 'button_confirm', cr)
732 proc_ids.append(proc_id)
733 wf_service = netsvc.LocalService("workflow")
734 wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
735 self.write(cr, uid, [production.id], {'picking_id':picking_id, 'move_lines': [(6,0,moves)], 'state':'confirmed'})
738 def force_production(self, cr, uid, ids, *args):
739 pick_obj = self.pool.get('stock.picking')
740 pick_obj.force_assign(cr, uid, [prod.picking_id.id for prod in self.browse(cr, uid, ids)])
746 class stock_move(osv.osv):
748 _inherit = 'stock.move'
750 'production_id': fields.many2one('mrp.production', 'Production', select=True),
754 class mrp_production_workcenter_line(osv.osv):
755 _name = 'mrp.production.workcenter.line'
756 _description = 'Work Orders'
759 'name': fields.char('Work Order', size=64, required=True),
760 'workcenter_id': fields.many2one('mrp.workcenter', 'Work Center', required=True),
761 'cycle': fields.float('Nbr of cycles', digits=(16,2)),
762 'hour': fields.float('Nbr of hours', digits=(16,2)),
763 'sequence': fields.integer('Sequence', required=True, help="Gives the sequence order when displaying a list of work orders."),
764 'production_id': fields.many2one('mrp.production', 'Production Order', select=True, ondelete='cascade'),
767 'sequence': lambda *a: 1,
768 'hour': lambda *a: 0,
769 'cycle': lambda *a: 0,
771 mrp_production_workcenter_line()
773 class mrp_production_product_line(osv.osv):
774 _name = 'mrp.production.product.line'
775 _description = 'Production scheduled products'
777 'name': fields.char('Name', size=64, required=True),
778 'product_id': fields.many2one('product.product', 'Product', required=True),
779 'product_qty': fields.float('Product Qty', required=True),
780 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True),
781 'product_uos_qty': fields.float('Product UOS Qty'),
782 'product_uos': fields.many2one('product.uom', 'Product UOS'),
783 'production_id': fields.many2one('mrp.production', 'Production Order', select=True),
785 mrp_production_product_line()
787 # ------------------------------------------------------------------
789 # ------------------------------------------------------------------
791 # Produce, Buy or Find products and place a move
792 # then wizard for picking lists & move
794 class mrp_procurement(osv.osv):
795 _name = "mrp.procurement"
796 _description = "Procurement"
797 _order = 'priority,date_planned'
799 'name': fields.char('Name', size=64, required=True),
800 'origin': fields.char('Origin', size=64,
801 help="Reference of the document that created this Requisition.\n"
802 "This is automatically completed by Open ERP."),
803 'priority': fields.selection([('0','Not urgent'),('1','Normal'),('2','Urgent'),('3','Very Urgent')], 'Priority', required=True),
804 'date_planned': fields.datetime('Scheduled date', required=True),
805 'date_close': fields.datetime('Date Closed'),
806 'product_id': fields.many2one('product.product', 'Product', required=True),
807 'product_qty': fields.float('Quantity', required=True, states={'draft':[('readonly',False)]}, readonly=True),
808 'product_uom': fields.many2one('product.uom', 'Product UoM', required=True, states={'draft':[('readonly',False)]}, readonly=True),
809 'product_uos_qty': fields.float('UoS Quantity', states={'draft':[('readonly',False)]}, readonly=True),
810 'product_uos': fields.many2one('product.uom', 'Product UoS', states={'draft':[('readonly',False)]}, readonly=True),
811 'move_id': fields.many2one('stock.move', 'Reservation', ondelete='set null'),
813 'bom_id': fields.many2one('mrp.bom', 'BoM', ondelete='cascade', select=True),
815 'close_move': fields.boolean('Close Move at end', required=True),
816 'location_id': fields.many2one('stock.location', 'Location', required=True),
817 'procure_method': fields.selection([('make_to_stock','from stock'),('make_to_order','on order')], 'Requisition Method', states={'draft':[('readonly',False)], 'confirmed':[('readonly',False)]},
818 readonly=True, required=True, help="If you encode manually a Requisition, you probably want to use" \
819 " a make to order method."),
821 'purchase_id': fields.many2one('purchase.order', 'Purchase Order'),
822 'note': fields.text('Note'),
824 'property_ids': fields.many2many('mrp.property', 'mrp_procurement_property_rel', 'procurement_id','property_id', 'Properties'),
826 'message': fields.char('Latest error', size=64),
827 'state': fields.selection([
829 ('confirmed','Confirmed'),
830 ('exception','Exception'),
831 ('running','Running'),
835 ('waiting','Waiting')], 'State', required=True,
836 help='When a procurement is created the state is set to \'Draft\'.\n If the procurement is confirmed, the state is set to \'Confirmed\'.\
837 \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.'),
838 'note' : fields.text('Note'),
839 'company_id': fields.many2one('res.company','Company',required=True),
842 'state': lambda *a: 'draft',
843 'priority': lambda *a: '1',
844 'date_planned': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
845 'close_move': lambda *a: 0,
846 'procure_method': lambda *a: 'make_to_order',
847 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'mrp.procurement', c)
850 def unlink(self, cr, uid, ids, context=None):
851 procurements = self.read(cr, uid, ids, ['state'])
853 for s in procurements:
854 if s['state'] in ['draft','cancel']:
855 unlink_ids.append(s['id'])
857 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Requisition Order(s) which are in %s State!' % s['state']))
858 return osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
860 def onchange_product_id(self, cr, uid, ids, product_id, context={}):
862 w=self.pool.get('product.product').browse(cr,uid,product_id, context)
864 'product_uom':w.uom_id.id,
865 'product_uos':w.uos_id and w.uos_id.id or w.uom_id.id
870 def check_product(self, cr, uid, ids):
871 for procurement in self.browse(cr, uid, ids):
872 if procurement.product_id.type in ('product', 'consu'):
876 def check_move_cancel(self, cr, uid, ids, context={}):
879 for procurement in self.browse(cr, uid, ids, context):
880 if procurement.move_id:
882 if not procurement.move_id.state=='cancel':
886 def check_move_done(self, cr, uid, ids, context={}):
888 for proc in self.browse(cr, uid, ids, context):
890 if not proc.move_id.state=='done':
895 # This method may be overrided by objects that override mrp.procurment
896 # for computing their own purpose
898 def _quantity_compute_get(self, cr, uid, proc, context={}):
899 if proc.product_id.type=='product':
900 if proc.move_id.product_uos:
901 return proc.move_id.product_uos_qty
904 def _uom_compute_get(self, cr, uid, proc, context={}):
905 if proc.product_id.type=='product':
906 if proc.move_id.product_uos:
907 return proc.move_id.product_uos.id
911 # Return the quantity of product shipped/produced/served, wich may be
912 # different from the planned quantity
914 def quantity_get(self, cr, uid, id, context={}):
915 proc = self.browse(cr, uid, id, context)
916 result = self._quantity_compute_get(cr, uid, proc, context)
918 result = proc.product_qty
921 def uom_get(self, cr, uid, id, context=None):
922 proc = self.browse(cr, uid, id, context)
923 result = self._uom_compute_get(cr, uid, proc, context)
925 result = proc.product_uom.id
928 def check_waiting(self, cr, uid, ids, context=[]):
929 for procurement in self.browse(cr, uid, ids, context=context):
930 if procurement.move_id and procurement.move_id.state=='auto':
934 def check_produce_service(self, cr, uid, procurement, context=[]):
937 def check_produce_product(self, cr, uid, procurement, context=[]):
938 properties = [x.id for x in procurement.property_ids]
939 bom_id = self.pool.get('mrp.bom')._bom_find(cr, uid, procurement.product_id.id, procurement.product_uom.id, properties)
941 cr.execute('update mrp_procurement set message=%s where id=%s', (_('No BoM defined for this product !'), procurement.id))
945 def check_make_to_stock(self, cr, uid, ids, context={}):
947 for procurement in self.browse(cr, uid, ids, context=context):
948 if procurement.product_id.type=='service':
949 ok = ok and self._check_make_to_stock_service(cr, uid, procurement, context)
951 ok = ok and self._check_make_to_stock_product(cr, uid, procurement, context)
954 def check_produce(self, cr, uid, ids, context={}):
956 user = self.pool.get('res.users').browse(cr, uid, uid)
957 for procurement in self.browse(cr, uid, ids):
958 if procurement.product_id.product_tmpl_id.supply_method<>'produce':
959 if procurement.product_id.seller_ids:
960 partner = procurement.product_id.seller_ids[0].name
961 if user.company_id and user.company_id.partner_id:
962 if partner.id == user.company_id.partner_id.id:
965 if procurement.product_id.product_tmpl_id.type=='service':
966 res = res and self.check_produce_service(cr, uid, procurement, context)
968 res = res and self.check_produce_product(cr, uid, procurement, context)
973 def check_buy(self, cr, uid, ids):
974 user = self.pool.get('res.users').browse(cr, uid, uid)
975 for procurement in self.browse(cr, uid, ids):
976 if procurement.product_id.product_tmpl_id.supply_method<>'buy':
978 if not procurement.product_id.seller_ids:
979 cr.execute('update mrp_procurement set message=%s where id=%s', (_('No supplier defined for this product !'), procurement.id))
981 partner = procurement.product_id.seller_ids[0].name
982 if user.company_id and user.company_id.partner_id:
983 if partner.id == user.company_id.partner_id.id:
985 address_id = self.pool.get('res.partner').address_get(cr, uid, [partner.id], ['delivery'])['delivery']
987 cr.execute('update mrp_procurement set message=%s where id=%s', (_('No address defined for the supplier'), procurement.id))
991 def test_cancel(self, cr, uid, ids):
992 for record in self.browse(cr, uid, ids):
993 if record.move_id and record.move_id.state=='cancel':
997 def action_confirm(self, cr, uid, ids, context={}):
998 for procurement in self.browse(cr, uid, ids):
999 if procurement.product_qty <= 0.00:
1000 raise osv.except_osv(_('Data Insufficient !'), _('Please check the Quantity of Requisition Order(s), it should not be less than 1!'))
1001 if procurement.product_id.type in ('product', 'consu'):
1002 if not procurement.move_id:
1003 source = procurement.location_id.id
1004 if procurement.procure_method=='make_to_order':
1005 source = procurement.product_id.product_tmpl_id.property_stock_procurement.id
1006 id = self.pool.get('stock.move').create(cr, uid, {
1007 'name': 'PROC:'+procurement.name,
1008 'location_id': source,
1009 'location_dest_id': procurement.location_id.id,
1010 'product_id': procurement.product_id.id,
1011 'product_qty':procurement.product_qty,
1012 'product_uom': procurement.product_uom.id,
1013 'date_planned': procurement.date_planned,
1014 'state':'confirmed',
1015 'company_id': procurement.company_id.id,
1017 self.write(cr, uid, [procurement.id], {'move_id': id, 'close_move':1})
1020 if procurement.procure_method=='make_to_stock' and procurement.move_id.state in ('waiting',):
1021 id = self.pool.get('stock.move').write(cr, uid, [procurement.move_id.id], {'state':'confirmed'})
1022 self.write(cr, uid, ids, {'state':'confirmed','message':''})
1025 def action_move_assigned(self, cr, uid, ids):
1026 self.write(cr, uid, ids, {'state':'running','message':_('from stock: products assigned.')})
1029 def _check_make_to_stock_service(self, cr, uid, procurement, context={}):
1032 def _check_make_to_stock_product(self, cr, uid, procurement, context={}):
1034 if procurement.move_id:
1035 id = procurement.move_id.id
1036 if not (procurement.move_id.state in ('done','assigned','cancel')):
1037 ok = ok and self.pool.get('stock.move').action_assign(cr, uid, [id])
1038 cr.execute('select count(id) from stock_warehouse_orderpoint where product_id=%s', (procurement.product_id.id,))
1039 if not cr.fetchone()[0]:
1040 cr.execute('update mrp_procurement set message=%s where id=%s', (_('from stock and no minimum orderpoint rule defined'), procurement.id))
1043 def action_produce_assign_service(self, cr, uid, ids, context={}):
1044 for procurement in self.browse(cr, uid, ids):
1045 self.write(cr, uid, [procurement.id], {'state':'running'})
1048 def action_produce_assign_product(self, cr, uid, ids, context={}):
1050 company = self.pool.get('res.users').browse(cr, uid, uid, context).company_id
1051 for procurement in self.browse(cr, uid, ids):
1052 res_id = procurement.move_id.id
1053 loc_id = procurement.location_id.id
1054 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)
1055 newdate = newdate - DateTime.RelativeDateTime(days=company.manufacturing_lead)
1056 produce_id = self.pool.get('mrp.production').create(cr, uid, {
1057 'origin': procurement.origin,
1058 'product_id': procurement.product_id.id,
1059 'product_qty': procurement.product_qty,
1060 'product_uom': procurement.product_uom.id,
1061 'product_uos_qty': procurement.product_uos and procurement.product_uos_qty or False,
1062 'product_uos': procurement.product_uos and procurement.product_uos.id or False,
1063 'location_src_id': procurement.location_id.id,
1064 'location_dest_id': procurement.location_id.id,
1065 'bom_id': procurement.bom_id and procurement.bom_id.id or False,
1066 'date_planned': newdate.strftime('%Y-%m-%d %H:%M:%S'),
1067 'move_prod_id': res_id,
1068 'company_id': procurement.company_id.id,
1070 self.write(cr, uid, [procurement.id], {'state':'running'})
1071 bom_result = self.pool.get('mrp.production').action_compute(cr, uid,
1072 [produce_id], properties=[x.id for x in procurement.property_ids])
1073 wf_service = netsvc.LocalService("workflow")
1074 wf_service.trg_validate(uid, 'mrp.production', produce_id, 'button_confirm', cr)
1075 self.pool.get('stock.move').write(cr, uid, [res_id],
1076 {'location_id':procurement.location_id.id})
1079 def action_po_assign(self, cr, uid, ids, context={}):
1081 company = self.pool.get('res.users').browse(cr, uid, uid, context).company_id
1082 for procurement in self.browse(cr, uid, ids):
1083 res_id = procurement.move_id.id
1084 partner = procurement.product_id.seller_ids[0].name
1085 partner_id = partner.id
1086 address_id = self.pool.get('res.partner').address_get(cr, uid, [partner_id], ['delivery'])['delivery']
1087 pricelist_id = partner.property_product_pricelist_purchase.id
1089 uom_id = procurement.product_id.uom_po_id.id
1091 qty = self.pool.get('product.uom')._compute_qty(cr, uid, procurement.product_uom.id, procurement.product_qty, uom_id)
1092 if procurement.product_id.seller_ids[0].qty:
1093 qty=max(qty,procurement.product_id.seller_ids[0].qty)
1095 price = self.pool.get('product.pricelist').price_get(cr, uid, [pricelist_id], procurement.product_id.id, qty, False, {'uom': uom_id})[pricelist_id]
1097 newdate = DateTime.strptime(procurement.date_planned, '%Y-%m-%d %H:%M:%S')
1098 newdate = newdate - DateTime.RelativeDateTime(days=company.po_lead)
1099 newdate = newdate - procurement.product_id.seller_ids[0].delay
1101 context.update({'lang':partner.lang})
1102 product=self.pool.get('product.product').browse(cr,uid,procurement.product_id.id,context=context)
1105 'name': product.name,
1107 'product_id': procurement.product_id.id,
1108 'product_uom': uom_id,
1109 'price_unit': price,
1110 'date_planned': newdate.strftime('%Y-%m-%d %H:%M:%S'),
1111 'move_dest_id': res_id,
1112 'notes':product.description_purchase,
1115 taxes_ids = procurement.product_id.product_tmpl_id.supplier_taxes_id
1116 taxes = self.pool.get('account.fiscal.position').map_tax(cr, uid, partner.property_account_position, taxes_ids)
1118 'taxes_id':[(6,0,taxes)]
1120 purchase_id = self.pool.get('purchase.order').create(cr, uid, {
1121 'origin': procurement.origin,
1122 'partner_id': partner_id,
1123 'partner_address_id': address_id,
1124 'location_id': procurement.location_id.id,
1125 'pricelist_id': pricelist_id,
1126 'order_line': [(0,0,line)],
1127 'company_id': procurement.company_id.id,
1128 'fiscal_position': partner.property_account_position and partner.property_account_position.id or False
1130 self.write(cr, uid, [procurement.id], {'state':'running', 'purchase_id':purchase_id})
1133 def action_cancel(self, cr, uid, ids):
1136 for proc in self.browse(cr, uid, ids):
1138 if proc.move_id.state not in ('done','cancel'):
1139 todo2.append(proc.move_id.id)
1141 if proc.move_id and proc.move_id.state=='waiting':
1142 todo.append(proc.move_id.id)
1144 self.pool.get('stock.move').action_cancel(cr, uid, todo2)
1146 self.pool.get('stock.move').write(cr, uid, todo, {'state':'assigned'})
1147 self.write(cr, uid, ids, {'state':'cancel'})
1148 wf_service = netsvc.LocalService("workflow")
1150 wf_service.trg_trigger(uid, 'mrp.procurement', id, cr)
1153 def action_check_finnished(self, cr, uid, ids):
1154 return self.check_move_done(cr, uid, ids)
1156 def action_check(self, cr, uid, ids):
1158 for procurement in self.browse(cr, uid, ids):
1159 if procurement.move_id.state=='assigned' or procurement.move_id.state=='done':
1160 self.action_done(cr, uid, [procurement.id])
1164 def action_ready(self, cr, uid, ids):
1165 res = self.write(cr, uid, ids, {'state':'ready'})
1168 def action_done(self, cr, uid, ids):
1169 for procurement in self.browse(cr, uid, ids):
1170 if procurement.move_id:
1171 if procurement.close_move and (procurement.move_id.state <> 'done'):
1172 self.pool.get('stock.move').action_done(cr, uid, [procurement.move_id.id])
1173 res = self.write(cr, uid, ids, {'state':'done', 'date_close':time.strftime('%Y-%m-%d')})
1174 wf_service = netsvc.LocalService("workflow")
1176 wf_service.trg_trigger(uid, 'mrp.procurement', id, cr)
1179 def run_scheduler(self, cr, uid, automatic=False, use_new_cursor=False, context=None):
1181 use_new_cursor: False or the dbname
1185 self._procure_confirm(cr, uid, use_new_cursor=use_new_cursor, context=context)
1186 self._procure_orderpoint_confirm(cr, uid, automatic=automatic,\
1187 use_new_cursor=use_new_cursor, context=context)
1191 class stock_warehouse_orderpoint(osv.osv):
1192 _name = "stock.warehouse.orderpoint"
1193 _description = "Orderpoint minimum rule"
1195 'name': fields.char('Name', size=32, required=True),
1196 'active': fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the orderpoint without removing it."),
1197 'logic': fields.selection([('max','Order to Max'),('price','Best price (not yet active!)')], 'Reordering Mode', required=True),
1198 'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse', required=True),
1199 'location_id': fields.many2one('stock.location', 'Location', required=True),
1200 'product_id': fields.many2one('product.product', 'Product', required=True, domain=[('type','=','product')]),
1201 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True ),
1202 'product_min_qty': fields.float('Min Quantity', required=True,
1203 help="When the virtual stock goes belong the Min Quantity, Open ERP generates "\
1204 "a requisition to bring the virtual stock to the Max Quantity."),
1205 'product_max_qty': fields.float('Max Quantity', required=True,
1206 help="When the virtual stock goes belong the Min Quantity, Open ERP generates "\
1207 "a requisition to bring the virtual stock to the Max Quantity."),
1208 'qty_multiple': fields.integer('Qty Multiple', required=True,
1209 help="The requisition quantity will by rounded up to this multiple."),
1210 'procurement_id': fields.many2one('mrp.procurement', 'Purchase Order'),
1211 'company_id': fields.many2one('res.company','Company',required=True),
1214 'active': lambda *a: 1,
1215 'logic': lambda *a: 'max',
1216 'qty_multiple': lambda *a: 1,
1217 'name': lambda x,y,z,c: x.pool.get('ir.sequence').get(y,z,'mrp.warehouse.orderpoint') or '',
1218 'product_uom': lambda sel, cr, uid, context: context.get('product_uom', False),
1219 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'stock.warehouse.orderpoint', c)
1221 def onchange_warehouse_id(self, cr, uid, ids, warehouse_id, context={}):
1223 w=self.pool.get('stock.warehouse').browse(cr,uid,warehouse_id, context)
1224 v = {'location_id':w.lot_stock_id.id}
1227 def onchange_product_id(self, cr, uid, ids, product_id, context={}):
1229 prod=self.pool.get('product.product').browse(cr,uid,product_id)
1230 v = {'product_uom':prod.uom_id.id}
1233 def copy(self, cr, uid, id, default=None,context={}):
1237 'name': self.pool.get('ir.sequence').get(cr, uid, 'mrp.warehouse.orderpoint') or '',
1239 return super(stock_warehouse_orderpoint, self).copy(cr, uid, id, default, context)
1240 stock_warehouse_orderpoint()
1243 class StockMove(osv.osv):
1244 _inherit = 'stock.move'
1246 'procurements': fields.one2many('mrp.procurement', 'move_id', 'Requisitions'),
1248 def copy(self, cr, uid, id, default=None, context=None):
1249 default = default or {}
1250 default['procurements'] = []
1251 return super(StockMove, self).copy(cr, uid, id, default, context)
1253 def _action_explode(self, cr, uid, move, context={}):
1254 if move.product_id.supply_method=='produce' and move.product_id.procure_method=='make_to_order':
1255 bis = self.pool.get('mrp.bom').search(cr, uid, [
1256 ('product_id','=',move.product_id.id),
1257 ('bom_id','=',False),
1258 ('type','=','phantom')])
1260 factor = move.product_qty
1261 bom_point = self.pool.get('mrp.bom').browse(cr, uid, bis[0])
1262 res = self.pool.get('mrp.bom')._bom_explode(cr, uid, bom_point, factor, [])
1263 dest = move.product_id.product_tmpl_id.property_stock_production.id
1265 if move.state=='assigned':
1270 'picking_id': move.picking_id.id,
1271 'product_id': line['product_id'],
1272 'product_uom': line['product_uom'],
1273 'product_qty': line['product_qty'],
1274 'product_uos': line['product_uos'],
1275 'product_uos_qty': line['product_uos_qty'],
1276 'move_dest_id': move.id,
1278 'name': line['name'],
1279 'location_dest_id': dest,
1280 'move_history_ids': [(6,0,[move.id])],
1281 'move_history_ids2': [(6,0,[])],
1284 mid = self.pool.get('stock.move').copy(cr, uid, move.id, default=valdef)
1285 prodobj = self.pool.get('product.product').browse(cr, uid, line['product_id'], context=context)
1286 proc_id = self.pool.get('mrp.procurement').create(cr, uid, {
1287 'name': (move.picking_id.origin or ''),
1288 'origin': (move.picking_id.origin or ''),
1289 'date_planned': move.date_planned,
1290 'product_id': line['product_id'],
1291 'product_qty': line['product_qty'],
1292 'product_uom': line['product_uom'],
1293 'product_uos_qty': line['product_uos'] and line['product_uos_qty'] or False,
1294 'product_uos': line['product_uos'],
1295 'location_id': move.location_id.id,
1296 'procure_method': prodobj.procure_method,
1298 'company_id': line['company_id'],
1300 wf_service = netsvc.LocalService("workflow")
1301 wf_service.trg_validate(uid, 'mrp.procurement', proc_id, 'button_confirm', cr)
1302 self.pool.get('stock.move').write(cr, uid, [move.id], {
1303 'location_id': move.location_dest_id.id,
1304 'auto_validate': True,
1305 'picking_id': False,
1306 'location_id': dest,
1309 for m in self.pool.get('mrp.procurement').search(cr, uid, [('move_id','=',move.id)], context):
1310 wf_service = netsvc.LocalService("workflow")
1311 wf_service.trg_validate(uid, 'mrp.procurement', m, 'button_wait_done', cr)
1316 class StockPicking(osv.osv):
1317 _inherit = 'stock.picking'
1319 def test_finnished(self, cursor, user, ids):
1320 wf_service = netsvc.LocalService("workflow")
1321 res = super(StockPicking, self).test_finnished(cursor, user, ids)
1322 for picking in self.browse(cursor, user, ids):
1323 for move in picking.move_lines:
1324 if move.state == 'done' and move.procurements:
1325 for procurement in move.procurements:
1326 wf_service.trg_validate(user, 'mrp.procurement',
1327 procurement.id, 'button_check', cursor)
1331 # Explode picking by replacing phantom BoMs
1333 def action_explode(self, cr, uid, picks, *args):
1334 for move in self.pool.get('stock.move').browse(cr, uid, picks):
1335 self.pool.get('stock.move')._action_explode(cr, uid, move)
1340 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: