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,
179 help= "If a sub-product is used in several products, it can be useful to create its own BoM."\
180 "Though if you don't want separated production orders for this sub-product, select Set/Phantom as BoM type."\
181 "If a Phantom BoM is used for a root product, it will be sold and shipped as a set of components, instead of being produced."),
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', help="Gives the sequence order when displaying a list of bills of material."),
186 'position': fields.char('Internal Reference', 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', help="Product UOS (Unit of Sale) is the unit of measurement for the invoicing and promotion of stock."),
190 'product_qty': fields.float('Product Qty', required=True),
191 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True, help="UoM (Unit of Measure) is the unit of measurement for the inventory control"),
192 'product_rounding': fields.float('Product Rounding', help="Rounding applied on the product quantity."),
193 'product_efficiency': fields.float('Product Efficiency', required=True, help="Material efficiency. 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 future loads on workcenters based on production planning."),
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')], 'Index type'),
200 'child_ids': fields.function(_child_compute,relation='mrp.bom', method=True, string="BoM Hierarchy", type='many2many'),
201 'child_complete_ids': fields.function(_child_compute,relation='mrp.bom', method=True, string="BoM Hierarchy", type='many2many'),
202 'company_id': fields.many2one('res.company','Company',required=True),
205 'active': lambda *a: 1,
206 'product_efficiency': lambda *a: 1.0,
207 'product_qty': lambda *a: 1.0,
208 'product_rounding': lambda *a: 1.0,
209 'type': lambda *a: 'normal',
210 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'mrp.bom', c)
214 ('bom_qty_zero', 'CHECK (product_qty>0)', 'All product quantities must be greater than 0.\n' \
215 'You should install the mrp_subproduct module if you want to manage extra products on BoMs !'),
218 def _check_recursion(self, cr, uid, ids):
221 cr.execute('select distinct bom_id from mrp_bom where id in ('+','.join(map(str, ids))+')')
222 ids = filter(None, map(lambda x:x[0], cr.fetchall()))
228 (_check_recursion, 'Error ! You can not create recursive BoM.', ['parent_id'])
232 def onchange_product_id(self, cr, uid, ids, product_id, name, context={}):
234 prod=self.pool.get('product.product').browse(cr,uid,[product_id])[0]
235 v = {'product_uom':prod.uom_id.id}
237 v['name'] = prod.name
241 def _bom_find(self, cr, uid, product_id, product_uom, properties=[]):
243 # Why searching on BoM without parent ?
244 cr.execute('select id from mrp_bom where product_id=%s and bom_id is null order by sequence', (product_id,))
245 ids = map(lambda x: x[0], cr.fetchall())
248 for bom in self.pool.get('mrp.bom').browse(cr, uid, ids):
250 for prop_id in bom.property_ids:
251 if prop_id.id in properties:
253 if (prop>max_prop) or ((max_prop==0) and not result):
258 def _bom_explode(self, cr, uid, bom, factor, properties, addthis=False, level=0):
259 factor = factor / (bom.product_efficiency or 1.0)
260 factor = rounding(factor, bom.product_rounding)
261 if factor<bom.product_rounding:
262 factor = bom.product_rounding
266 if bom.type=='phantom' and not bom.bom_lines:
267 newbom = self._bom_find(cr, uid, bom.product_id.id, bom.product_uom.id, properties)
269 res = self._bom_explode(cr, uid, self.browse(cr, uid, [newbom])[0], factor*bom.product_qty, properties, addthis=True, level=level+10)
270 result = result + res[0]
271 result2 = result2 + res[1]
276 if addthis and not bom.bom_lines:
279 'name': bom.product_id.name,
280 'product_id': bom.product_id.id,
281 'product_qty': bom.product_qty * factor,
282 'product_uom': bom.product_uom.id,
283 'product_uos_qty': bom.product_uos and bom.product_uos_qty * factor or False,
284 'product_uos': bom.product_uos and bom.product_uos.id or False,
287 for wc_use in bom.routing_id.workcenter_lines:
288 wc = wc_use.workcenter_id
289 d, m = divmod(factor, wc_use.workcenter_id.capacity_per_cycle)
290 mult = (d + (m and 1.0 or 0.0))
291 cycle = mult * wc_use.cycle_nbr
293 'name': bom.routing_id.name,
294 'workcenter_id': wc.id,
295 'sequence': level+(wc_use.sequence or 0),
297 'hour': float(wc_use.hour_nbr*mult + (wc.time_start+wc.time_stop+cycle*wc.time_cycle) * (wc.time_efficiency or 1.0)),
299 for bom2 in bom.bom_lines:
300 res = self._bom_explode(cr, uid, bom2, factor, properties, addthis=True, level=level+10)
301 result = result + res[0]
302 result2 = result2 + res[1]
303 return result, result2
305 def set_indices(self, cr, uid, ids, context = {}):
306 if not ids or (ids and not ids[0]):
308 res = self.read(cr, uid, ids, ['revision_ids', 'revision_type'])
309 rev_ids = res[0]['revision_ids']
312 for rev_id in rev_ids:
313 if res[0]['revision_type'] == 'numeric':
314 self.pool.get('mrp.bom.revision').write(cr, uid, [rev_id], {'indice' : idx})
316 self.pool.get('mrp.bom.revision').write(cr, uid, [rev_id], {'indice' : "%c"%(idx+96,)})
322 class mrp_bom_revision(osv.osv):
323 _name = 'mrp.bom.revision'
324 _description = 'Bill of material revisions'
326 'name': fields.char('Modification name', size=64, required=True),
327 'description': fields.text('Description'),
328 'date': fields.date('Modification Date'),
329 'indice': fields.char('Revision', size=16),
330 'last_indice': fields.char('last indice', size=64),
331 'author_id': fields.many2one('res.users', 'Author'),
332 'bom_id': fields.many2one('mrp.bom', 'BoM', select=True),
336 'author_id': lambda x,y,z,c: z,
337 'date': lambda *a: time.strftime('%Y-%m-%d'),
345 return round(f / r) * r
347 class mrp_production(osv.osv):
348 _name = 'mrp.production'
349 _description = 'Production'
350 _date_name = 'date_planned'
352 def _get_sale_order(self,cr,uid,ids,field_name=False):
353 move_obj=self.pool.get('stock.move')
354 def get_parent_move(move_id):
355 move = move_obj.browse(cr,uid,move_id)
356 if move.move_dest_id:
357 return get_parent_move(move.move_dest_id.id)
359 productions=self.read(cr,uid,ids,['id','move_prod_id'])
361 for production in productions:
362 res[production['id']]=False
363 if production.get('move_prod_id',False):
364 parent_move_line=get_parent_move(production['move_prod_id'][0])
366 move = move_obj.browse(cr,uid,parent_move_line)
367 #TODO: fix me sale module can not be used here,
368 #as may be mrp can be installed without sale module
369 if field_name=='name':
370 res[production['id']]=move.sale_line_id and move.sale_line_id.order_id.name or False
371 if field_name=='client_order_ref':
372 res[production['id']]=move.sale_line_id and move.sale_line_id.order_id.client_order_ref or False
375 def _production_calc(self, cr, uid, ids, prop, unknow_none, context={}):
377 for prod in self.browse(cr, uid, ids, context=context):
382 for wc in prod.workcenter_lines:
383 result[prod.id]['hour_total'] += wc.hour
384 result[prod.id]['cycle_total'] += wc.cycle
387 def _production_date_end(self, cr, uid, ids, prop, unknow_none, context={}):
389 for prod in self.browse(cr, uid, ids, context=context):
390 result[prod.id] = prod.date_planned
393 def _production_date(self, cr, uid, ids, prop, unknow_none, context={}):
395 for prod in self.browse(cr, uid, ids, context=context):
396 result[prod.id] = prod.date_planned[:10]
399 def _sale_name_calc(self, cr, uid, ids, prop, unknow_none, unknow_dict):
400 return self._get_sale_order(cr,uid,ids,field_name='name')
402 def _sale_ref_calc(self, cr, uid, ids, prop, unknow_none, unknow_dict):
403 return self._get_sale_order(cr,uid,ids,field_name='client_order_ref')
406 'name': fields.char('Reference', size=64, required=True),
407 'origin': fields.char('Origin', size=64, help="Reference of the document that generated this production order request."),
408 'priority': fields.selection([('0','Not urgent'),('1','Normal'),('2','Urgent'),('3','Very Urgent')], 'Priority'),
410 'product_id': fields.many2one('product.product', 'Product', required=True, domain=[('type','<>','service')]),
411 'product_qty': fields.float('Product Qty', required=True, states={'draft':[('readonly',False)]}, readonly=True),
412 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True, states={'draft':[('readonly',False)]}, readonly=True),
413 'product_uos_qty': fields.float('Product UoS Qty', states={'draft':[('readonly',False)]}, readonly=True),
414 'product_uos': fields.many2one('product.uom', 'Product UoS', states={'draft':[('readonly',False)]}, readonly=True),
416 'location_src_id': fields.many2one('stock.location', 'Raw Materials Location', required=True,
417 help="Location where the system will look for components."),
418 'location_dest_id': fields.many2one('stock.location', 'Finished Products Location', required=True,
419 help="Location where the system will stock the finished products."),
421 'date_planned_end': fields.function(_production_date_end, method=True, type='date', string='Scheduled End'),
422 'date_planned_date': fields.function(_production_date, method=True, type='date', string='Scheduled Date'),
423 'date_planned': fields.datetime('Scheduled date', required=True, select=1),
424 'date_start': fields.datetime('Start Date'),
425 'date_finnished': fields.datetime('End Date'),
427 'bom_id': fields.many2one('mrp.bom', 'Bill of Material', domain=[('bom_id','=',False)]),
428 '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."),
430 'picking_id': fields.many2one('stock.picking', 'Picking list', readonly=True,
431 help="This is the internal picking list that brings the finished product to the production plan"),
432 'move_prod_id': fields.many2one('stock.move', 'Move product', readonly=True),
433 'move_lines': fields.many2many('stock.move', 'mrp_production_move_ids', 'production_id', 'move_id', 'Products Consummed'),
435 'move_created_ids': fields.one2many('stock.move', 'production_id', 'Moves Created'),
436 'product_lines': fields.one2many('mrp.production.product.line', 'production_id', 'Scheduled goods'),
437 'workcenter_lines': fields.one2many('mrp.production.workcenter.line', 'production_id', 'Work Centers Utilisation'),
438 '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,
439 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\'.\
440 \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\'.'),
441 'hour_total': fields.function(_production_calc, method=True, type='float', string='Total Hours', multi='workorder'),
442 'cycle_total': fields.function(_production_calc, method=True, type='float', string='Total Cycles', multi='workorder'),
444 'sale_name': fields.function(_sale_name_calc, method=True, type='char', string='Sale Name', help='Gives the name of sale order.'),
445 'sale_ref': fields.function(_sale_ref_calc, method=True, type='char', string='Sale Reference', help='Gives the name of Customer Reference from sale order.'),
446 'company_id': fields.many2one('res.company','Company',required=True),
449 'priority': lambda *a: '1',
450 'state': lambda *a: 'draft',
451 'date_planned': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
452 'product_qty': lambda *a: 1.0,
453 'name': lambda x,y,z,c: x.pool.get('ir.sequence').get(y,z,'mrp.production') or '/',
454 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'mrp.production', c),
456 _order = 'date_planned asc, priority desc';
457 def unlink(self, cr, uid, ids, context=None):
458 productions = self.read(cr, uid, ids, ['state'])
460 for s in productions:
461 if s['state'] in ['draft','cancel']:
462 unlink_ids.append(s['id'])
464 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Production Order(s) which are in %s State!' % s['state']))
465 return osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
467 def copy(self, cr, uid, id, default=None,context=None):
471 'name': self.pool.get('ir.sequence').get(cr, uid, 'mrp.production'),
473 'move_created_ids': [],
476 return super(mrp_production, self).copy(cr, uid, id, default, context)
478 def location_id_change(self, cr, uid, ids, src, dest, context={}):
482 return {'value': {'location_dest_id': src}}
485 def product_id_change(self, cr, uid, ids, product):
488 res = self.pool.get('product.product').read(cr, uid, [product], ['uom_id'])[0]
489 uom = res['uom_id'] and res['uom_id'][0]
490 result = {'product_uom':uom}
491 return {'value':result}
493 def bom_id_change(self, cr, uid, ids, product):
496 res = self.pool.get('mrp.bom').read(cr, uid, [product], ['routing_id'])[0]
497 routing_id = res['routing_id'] and res['routing_id'][0]
498 result = {'routing_id':routing_id}
499 return {'value':result}
501 def action_picking_except(self, cr, uid, ids):
502 self.write(cr, uid, ids, {'state':'picking_except'})
505 def action_compute(self, cr, uid, ids, properties=[]):
507 for production in self.browse(cr, uid, ids):
508 cr.execute('delete from mrp_production_product_line where production_id=%s', (production.id,))
509 cr.execute('delete from mrp_production_workcenter_line where production_id=%s', (production.id,))
510 bom_point = production.bom_id
511 bom_id = production.bom_id.id
513 bom_id = self.pool.get('mrp.bom')._bom_find(cr, uid, production.product_id.id, production.product_uom.id, properties)
515 bom_point = self.pool.get('mrp.bom').browse(cr, uid, bom_id)
516 routing_id = bom_point.routing_id.id or False
517 self.write(cr, uid, [production.id], {'bom_id': bom_id, 'routing_id': routing_id})
520 raise osv.except_osv(_('Error'), _("Couldn't find bill of material for product"))
522 #if bom_point.routing_id and bom_point.routing_id.location_id:
523 # self.write(cr, uid, [production.id], {'location_src_id': bom_point.routing_id.location_id.id})
525 factor = production.product_qty * production.product_uom.factor / bom_point.product_uom.factor
526 res = self.pool.get('mrp.bom')._bom_explode(cr, uid, bom_point, factor / bom_point.product_qty, properties)
530 line['production_id'] = production.id
531 self.pool.get('mrp.production.product.line').create(cr, uid, line)
532 for line in results2:
533 line['production_id'] = production.id
534 self.pool.get('mrp.production.workcenter.line').create(cr, uid, line)
537 def action_cancel(self, cr, uid, ids):
538 for production in self.browse(cr, uid, ids):
539 if production.move_created_ids:
540 self.pool.get('stock.move').action_cancel(cr, uid, [x.id for x in production.move_created_ids])
541 self.pool.get('stock.move').action_cancel(cr, uid, [x.id for x in production.move_lines])
542 self.write(cr, uid, ids, {'state':'cancel'}) #,'move_lines':[(6,0,[])]})
545 #XXX: may be a bug here; lot_lines are unreserved for a few seconds;
546 # between the end of the picking list and the call to this function
547 def action_ready(self, cr, uid, ids):
548 self.write(cr, uid, ids, {'state':'ready'})
549 for production in self.browse(cr, uid, ids):
550 if production.move_prod_id:
551 self.pool.get('stock.move').write(cr, uid, [production.move_prod_id.id],
552 {'location_id':production.location_dest_id.id})
555 #TODO Review materials in function in_prod and prod_end.
556 def action_production_end(self, cr, uid, ids):
558 for production in self.browse(cr, uid, ids):
559 for res in production.move_lines:
560 for move in production.move_created_ids:
561 #XXX must use the orm
562 cr.execute('INSERT INTO stock_move_history_ids \
563 (parent_id, child_id) VALUES (%s,%s)',
565 move_ids.append(res.id)
566 vals= {'state':'confirmed'}
567 new_moves = [x.id for x in production.move_created_ids]
568 self.pool.get('stock.move').write(cr, uid, new_moves, vals)
569 if not production.date_finnished:
570 self.write(cr, uid, [production.id],
571 {'date_finnished': time.strftime('%Y-%m-%d %H:%M:%S')})
572 self.pool.get('stock.move').check_assign(cr, uid, new_moves)
573 self.pool.get('stock.move').action_done(cr, uid, new_moves)
574 self._costs_generate(cr, uid, production)
575 self.pool.get('stock.move').action_done(cr, uid, move_ids)
576 self.write(cr, uid, ids, {'state': 'done'})
579 def _costs_generate(self, cr, uid, production):
581 for wc_line in production.workcenter_lines:
582 wc = wc_line.workcenter_id
583 if wc.costs_journal_id and wc.costs_general_account_id:
584 value = wc_line.hour * wc.costs_hour
585 account = wc.costs_hour_account_id.id
586 if value and account:
588 self.pool.get('account.analytic.line').create(cr, uid, {
589 'name': wc_line.name+' (H)',
591 'account_id': account,
592 'general_account_id': wc.costs_general_account_id.id,
593 'journal_id': wc.costs_journal_id.id,
596 if wc.costs_journal_id and wc.costs_general_account_id:
597 value = wc_line.cycle * wc.costs_cycle
598 account = wc.costs_cycle_account_id.id
599 if value and account:
601 self.pool.get('account.analytic.line').create(cr, uid, {
602 'name': wc_line.name+' (C)',
604 'account_id': account,
605 'general_account_id': wc.costs_general_account_id.id,
606 'journal_id': wc.costs_journal_id.id,
611 def action_in_production(self, cr, uid, ids):
613 for production in self.browse(cr, uid, ids):
614 for res in production.move_lines:
615 move_ids.append(res.id)
616 if not production.date_start:
617 self.write(cr, uid, [production.id],
618 {'date_start': time.strftime('%Y-%m-%d %H:%M:%S')})
619 self.pool.get('stock.move').action_done(cr, uid, move_ids)
620 self.write(cr, uid, ids, {'state': 'in_production'})
623 def test_if_product(self, cr, uid, ids):
625 for production in self.browse(cr, uid, ids):
626 if not production.product_lines:
627 if not self.action_compute(cr, uid, [production.id]):
631 def _get_auto_picking(self, cr, uid, production):
634 def action_confirm(self, cr, uid, ids):
637 for production in self.browse(cr, uid, ids):
638 if not production.product_lines:
639 self.action_compute(cr, uid, [production.id])
640 production = self.browse(cr, uid, [production.id])[0]
642 pick_type = 'internal'
644 if production.bom_id.routing_id and production.bom_id.routing_id.location_id:
645 routing_loc = production.bom_id.routing_id.location_id
646 if routing_loc.usage<>'internal':
648 address_id = routing_loc.address_id and routing_loc.address_id.id or False
649 routing_loc = routing_loc.id
650 picking_id = self.pool.get('stock.picking').create(cr, uid, {
651 'origin': (production.origin or '').split(':')[0] +':'+production.name,
655 'address_id': address_id,
656 'auto_picking': self._get_auto_picking(cr, uid, production),
657 'company_id': production.company_id.id,
660 source = production.product_id.product_tmpl_id.property_stock_production.id
662 'name':'PROD:'+production.name,
663 'date_planned': production.date_planned,
664 'product_id': production.product_id.id,
665 'product_qty': production.product_qty,
666 'product_uom': production.product_uom.id,
667 'product_uos_qty': production.product_uos and production.product_uos_qty or False,
668 'product_uos': production.product_uos and production.product_uos.id or False,
669 'location_id': source,
670 'location_dest_id': production.location_dest_id.id,
671 'move_dest_id': production.move_prod_id.id,
673 'company_id': production.company_id.id,
675 res_final_id = self.pool.get('stock.move').create(cr, uid, data)
677 self.write(cr, uid, [production.id], {'move_created_ids': [(6, 0, [res_final_id])]})
679 for line in production.product_lines:
681 newdate = production.date_planned
682 if line.product_id.type in ('product', 'consu'):
683 res_dest_id = self.pool.get('stock.move').create(cr, uid, {
684 'name':'PROD:'+production.name,
685 'date_planned': production.date_planned,
686 'product_id': line.product_id.id,
687 'product_qty': line.product_qty,
688 'product_uom': line.product_uom.id,
689 'product_uos_qty': line.product_uos and line.product_uos_qty or False,
690 'product_uos': line.product_uos and line.product_uos.id or False,
691 'location_id': routing_loc or production.location_src_id.id,
692 'location_dest_id': source,
693 'move_dest_id': res_final_id,
695 'company_id': production.company_id.id,
697 moves.append(res_dest_id)
698 move_id = self.pool.get('stock.move').create(cr, uid, {
699 'name':'PROD:'+production.name,
700 'picking_id':picking_id,
701 'product_id': line.product_id.id,
702 'product_qty': line.product_qty,
703 'product_uom': line.product_uom.id,
704 'product_uos_qty': line.product_uos and line.product_uos_qty or False,
705 'product_uos': line.product_uos and line.product_uos.id or False,
706 'date_planned': newdate,
707 'move_dest_id': res_dest_id,
708 'location_id': production.location_src_id.id,
709 'location_dest_id': routing_loc or production.location_src_id.id,
711 'company_id': production.company_id.id,
713 proc_id = self.pool.get('mrp.procurement').create(cr, uid, {
714 'name': (production.origin or '').split(':')[0] + ':' + production.name,
715 'origin': (production.origin or '').split(':')[0] + ':' + production.name,
716 'date_planned': newdate,
717 'product_id': line.product_id.id,
718 'product_qty': line.product_qty,
719 'product_uom': line.product_uom.id,
720 'product_uos_qty': line.product_uos and line.product_qty or False,
721 'product_uos': line.product_uos and line.product_uos.id or False,
722 'location_id': production.location_src_id.id,
723 'procure_method': line.product_id.procure_method,
725 'company_id': production.company_id.id,
727 wf_service = netsvc.LocalService("workflow")
728 wf_service.trg_validate(uid, 'mrp.procurement', proc_id, 'button_confirm', cr)
729 proc_ids.append(proc_id)
730 wf_service = netsvc.LocalService("workflow")
731 wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
732 self.write(cr, uid, [production.id], {'picking_id':picking_id, 'move_lines': [(6,0,moves)], 'state':'confirmed'})
735 def force_production(self, cr, uid, ids, *args):
736 pick_obj = self.pool.get('stock.picking')
737 pick_obj.force_assign(cr, uid, [prod.picking_id.id for prod in self.browse(cr, uid, ids)])
743 class stock_move(osv.osv):
745 _inherit = 'stock.move'
747 'production_id': fields.many2one('mrp.production', 'Production', select=True),
751 class mrp_production_workcenter_line(osv.osv):
752 _name = 'mrp.production.workcenter.line'
753 _description = 'Work Orders'
756 'name': fields.char('Work Order', size=64, required=True),
757 'workcenter_id': fields.many2one('mrp.workcenter', 'Work Center', required=True),
758 'cycle': fields.float('Nbr of cycles', digits=(16,2)),
759 'hour': fields.float('Nbr of hours', digits=(16,2)),
760 'sequence': fields.integer('Sequence', required=True, help="Gives the sequence order when displaying a list of work orders."),
761 'production_id': fields.many2one('mrp.production', 'Production Order', select=True, ondelete='cascade'),
764 'sequence': lambda *a: 1,
765 'hour': lambda *a: 0,
766 'cycle': lambda *a: 0,
768 mrp_production_workcenter_line()
770 class mrp_production_product_line(osv.osv):
771 _name = 'mrp.production.product.line'
772 _description = 'Production scheduled products'
774 'name': fields.char('Name', size=64, required=True),
775 'product_id': fields.many2one('product.product', 'Product', required=True),
776 'product_qty': fields.float('Product Qty', required=True),
777 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True),
778 'product_uos_qty': fields.float('Product UOS Qty'),
779 'product_uos': fields.many2one('product.uom', 'Product UOS'),
780 'production_id': fields.many2one('mrp.production', 'Production Order', select=True),
782 mrp_production_product_line()
784 # ------------------------------------------------------------------
786 # ------------------------------------------------------------------
788 # Produce, Buy or Find products and place a move
789 # then wizard for picking lists & move
791 class mrp_procurement(osv.osv):
792 _name = "mrp.procurement"
793 _description = "Procurement"
794 _order = 'priority,date_planned'
796 'name': fields.char('Name', size=64, required=True, help='Requisition name. It differs when done by scheduler else same as origin name.'),
797 'origin': fields.char('Origin', size=64,
798 help="Reference of the document that created this Requisition.\n"
799 "This is automatically completed by Open ERP."),
800 'priority': fields.selection([('0','Not urgent'),('1','Normal'),('2','Urgent'),('3','Very Urgent')], 'Priority', required=True),
801 'date_planned': fields.datetime('Scheduled date', required=True),
802 'date_close': fields.datetime('Date Closed'),
803 'product_id': fields.many2one('product.product', 'Product', required=True),
804 'product_qty': fields.float('Quantity', required=True, states={'draft':[('readonly',False)]}, readonly=True),
805 'product_uom': fields.many2one('product.uom', 'Product UoM', required=True, states={'draft':[('readonly',False)]}, readonly=True),
806 'product_uos_qty': fields.float('UoS Quantity', states={'draft':[('readonly',False)]}, readonly=True),
807 'product_uos': fields.many2one('product.uom', 'Product UoS', states={'draft':[('readonly',False)]}, readonly=True),
808 'move_id': fields.many2one('stock.move', 'Reservation', ondelete='set null'),
810 'bom_id': fields.many2one('mrp.bom', 'BoM', ondelete='cascade', select=True),
812 'close_move': fields.boolean('Close Move at end', required=True),
813 'location_id': fields.many2one('stock.location', 'Location', required=True),
814 'procure_method': fields.selection([('make_to_stock','from stock'),('make_to_order','on order')], 'Requisition Method', states={'draft':[('readonly',False)], 'confirmed':[('readonly',False)]},
815 readonly=True, required=True, help="If you encode manually a Requisition, you probably want to use" \
816 " a make to order method."),
818 'purchase_id': fields.many2one('purchase.order', 'Purchase Order'),
819 'note': fields.text('Note'),
821 'property_ids': fields.many2many('mrp.property', 'mrp_procurement_property_rel', 'procurement_id','property_id', 'Properties'),
823 'message': fields.char('Latest error', size=64),
824 'state': fields.selection([
826 ('confirmed','Confirmed'),
827 ('exception','Exception'),
828 ('running','Running'),
832 ('waiting','Waiting')], 'State', required=True,
833 help='When a procurement is created the state is set to \'Draft\'.\n If the procurement is confirmed, the state is set to \'Confirmed\'.\
834 \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.'),
835 'note' : fields.text('Note'),
836 'company_id': fields.many2one('res.company','Company',required=True),
839 'state': lambda *a: 'draft',
840 'priority': lambda *a: '1',
841 'date_planned': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
842 'close_move': lambda *a: 0,
843 'procure_method': lambda *a: 'make_to_order',
844 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'mrp.procurement', c)
847 def unlink(self, cr, uid, ids, context=None):
848 procurements = self.read(cr, uid, ids, ['state'])
850 for s in procurements:
851 if s['state'] in ['draft','cancel']:
852 unlink_ids.append(s['id'])
854 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Requisition Order(s) which are in %s State!' % s['state']))
855 return osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
857 def onchange_product_id(self, cr, uid, ids, product_id, context={}):
859 w=self.pool.get('product.product').browse(cr,uid,product_id, context)
861 'product_uom':w.uom_id.id,
862 'product_uos':w.uos_id and w.uos_id.id or w.uom_id.id
867 def check_product(self, cr, uid, ids):
868 for procurement in self.browse(cr, uid, ids):
869 if procurement.product_id.type in ('product', 'consu'):
873 def check_move_cancel(self, cr, uid, ids, context={}):
876 for procurement in self.browse(cr, uid, ids, context):
877 if procurement.move_id:
879 if not procurement.move_id.state=='cancel':
883 def check_move_done(self, cr, uid, ids, context={}):
885 for proc in self.browse(cr, uid, ids, context):
887 if not proc.move_id.state=='done':
892 # This method may be overrided by objects that override mrp.procurment
893 # for computing their own purpose
895 def _quantity_compute_get(self, cr, uid, proc, context={}):
896 if proc.product_id.type=='product':
897 if proc.move_id.product_uos:
898 return proc.move_id.product_uos_qty
901 def _uom_compute_get(self, cr, uid, proc, context={}):
902 if proc.product_id.type=='product':
903 if proc.move_id.product_uos:
904 return proc.move_id.product_uos.id
908 # Return the quantity of product shipped/produced/served, wich may be
909 # different from the planned quantity
911 def quantity_get(self, cr, uid, id, context={}):
912 proc = self.browse(cr, uid, id, context)
913 result = self._quantity_compute_get(cr, uid, proc, context)
915 result = proc.product_qty
918 def uom_get(self, cr, uid, id, context=None):
919 proc = self.browse(cr, uid, id, context)
920 result = self._uom_compute_get(cr, uid, proc, context)
922 result = proc.product_uom.id
925 def check_waiting(self, cr, uid, ids, context=[]):
926 for procurement in self.browse(cr, uid, ids, context=context):
927 if procurement.move_id and procurement.move_id.state=='auto':
931 def check_produce_service(self, cr, uid, procurement, context=[]):
934 def check_produce_product(self, cr, uid, procurement, context=[]):
935 properties = [x.id for x in procurement.property_ids]
936 bom_id = self.pool.get('mrp.bom')._bom_find(cr, uid, procurement.product_id.id, procurement.product_uom.id, properties)
938 cr.execute('update mrp_procurement set message=%s where id=%s', (_('No BoM defined for this product !'), procurement.id))
942 def check_make_to_stock(self, cr, uid, ids, context={}):
944 for procurement in self.browse(cr, uid, ids, context=context):
945 if procurement.product_id.type=='service':
946 ok = ok and self._check_make_to_stock_service(cr, uid, procurement, context)
948 ok = ok and self._check_make_to_stock_product(cr, uid, procurement, context)
951 def check_produce(self, cr, uid, ids, context={}):
953 user = self.pool.get('res.users').browse(cr, uid, uid)
954 for procurement in self.browse(cr, uid, ids):
955 if procurement.product_id.product_tmpl_id.supply_method<>'produce':
956 if procurement.product_id.seller_ids:
957 partner = procurement.product_id.seller_ids[0].name
958 if user.company_id and user.company_id.partner_id:
959 if partner.id == user.company_id.partner_id.id:
962 if procurement.product_id.product_tmpl_id.type=='service':
963 res = res and self.check_produce_service(cr, uid, procurement, context)
965 res = res and self.check_produce_product(cr, uid, procurement, context)
970 def check_buy(self, cr, uid, ids):
971 user = self.pool.get('res.users').browse(cr, uid, uid)
972 for procurement in self.browse(cr, uid, ids):
973 if procurement.product_id.product_tmpl_id.supply_method<>'buy':
975 if not procurement.product_id.seller_ids:
976 cr.execute('update mrp_procurement set message=%s where id=%s', (_('No supplier defined for this product !'), procurement.id))
978 partner = procurement.product_id.seller_ids[0].name
979 if user.company_id and user.company_id.partner_id:
980 if partner.id == user.company_id.partner_id.id:
982 address_id = self.pool.get('res.partner').address_get(cr, uid, [partner.id], ['delivery'])['delivery']
984 cr.execute('update mrp_procurement set message=%s where id=%s', (_('No address defined for the supplier'), procurement.id))
988 def test_cancel(self, cr, uid, ids):
989 for record in self.browse(cr, uid, ids):
990 if record.move_id and record.move_id.state=='cancel':
994 def action_confirm(self, cr, uid, ids, context={}):
995 for procurement in self.browse(cr, uid, ids):
996 if procurement.product_qty <= 0.00:
997 raise osv.except_osv(_('Data Insufficient !'), _('Please check the Quantity of Requisition Order(s), it should not be less than 1!'))
998 if procurement.product_id.type in ('product', 'consu'):
999 if not procurement.move_id:
1000 source = procurement.location_id.id
1001 if procurement.procure_method=='make_to_order':
1002 source = procurement.product_id.product_tmpl_id.property_stock_procurement.id
1003 id = self.pool.get('stock.move').create(cr, uid, {
1004 'name': 'PROC:'+procurement.name,
1005 'location_id': source,
1006 'location_dest_id': procurement.location_id.id,
1007 'product_id': procurement.product_id.id,
1008 'product_qty':procurement.product_qty,
1009 'product_uom': procurement.product_uom.id,
1010 'date_planned': procurement.date_planned,
1011 'state':'confirmed',
1012 'company_id': procurement.company_id.id,
1014 self.write(cr, uid, [procurement.id], {'move_id': id, 'close_move':1})
1017 if procurement.procure_method=='make_to_stock' and procurement.move_id.state in ('waiting',):
1018 id = self.pool.get('stock.move').write(cr, uid, [procurement.move_id.id], {'state':'confirmed'})
1019 self.write(cr, uid, ids, {'state':'confirmed','message':''})
1022 def action_move_assigned(self, cr, uid, ids):
1023 self.write(cr, uid, ids, {'state':'running','message':_('from stock: products assigned.')})
1026 def _check_make_to_stock_service(self, cr, uid, procurement, context={}):
1029 def _check_make_to_stock_product(self, cr, uid, procurement, context={}):
1031 if procurement.move_id:
1032 id = procurement.move_id.id
1033 if not (procurement.move_id.state in ('done','assigned','cancel')):
1034 ok = ok and self.pool.get('stock.move').action_assign(cr, uid, [id])
1035 cr.execute('select count(id) from stock_warehouse_orderpoint where product_id=%s', (procurement.product_id.id,))
1036 if not cr.fetchone()[0]:
1037 cr.execute('update mrp_procurement set message=%s where id=%s', (_('from stock and no minimum orderpoint rule defined'), procurement.id))
1040 def action_produce_assign_service(self, cr, uid, ids, context={}):
1041 for procurement in self.browse(cr, uid, ids):
1042 self.write(cr, uid, [procurement.id], {'state':'running'})
1045 def action_produce_assign_product(self, cr, uid, ids, context={}):
1047 company = self.pool.get('res.users').browse(cr, uid, uid, context).company_id
1048 for procurement in self.browse(cr, uid, ids):
1049 res_id = procurement.move_id.id
1050 loc_id = procurement.location_id.id
1051 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)
1052 newdate = newdate - DateTime.RelativeDateTime(days=company.manufacturing_lead)
1053 produce_id = self.pool.get('mrp.production').create(cr, uid, {
1054 'origin': procurement.origin,
1055 'product_id': procurement.product_id.id,
1056 'product_qty': procurement.product_qty,
1057 'product_uom': procurement.product_uom.id,
1058 'product_uos_qty': procurement.product_uos and procurement.product_uos_qty or False,
1059 'product_uos': procurement.product_uos and procurement.product_uos.id or False,
1060 'location_src_id': procurement.location_id.id,
1061 'location_dest_id': procurement.location_id.id,
1062 'bom_id': procurement.bom_id and procurement.bom_id.id or False,
1063 'date_planned': newdate.strftime('%Y-%m-%d %H:%M:%S'),
1064 'move_prod_id': res_id,
1065 'company_id': procurement.company_id.id,
1067 self.write(cr, uid, [procurement.id], {'state':'running'})
1068 bom_result = self.pool.get('mrp.production').action_compute(cr, uid,
1069 [produce_id], properties=[x.id for x in procurement.property_ids])
1070 wf_service = netsvc.LocalService("workflow")
1071 wf_service.trg_validate(uid, 'mrp.production', produce_id, 'button_confirm', cr)
1072 self.pool.get('stock.move').write(cr, uid, [res_id],
1073 {'location_id':procurement.location_id.id})
1076 def action_po_assign(self, cr, uid, ids, context={}):
1078 company = self.pool.get('res.users').browse(cr, uid, uid, context).company_id
1079 for procurement in self.browse(cr, uid, ids):
1080 res_id = procurement.move_id.id
1081 partner = procurement.product_id.seller_ids[0].name
1082 partner_id = partner.id
1083 address_id = self.pool.get('res.partner').address_get(cr, uid, [partner_id], ['delivery'])['delivery']
1084 pricelist_id = partner.property_product_pricelist_purchase.id
1086 uom_id = procurement.product_id.uom_po_id.id
1088 qty = self.pool.get('product.uom')._compute_qty(cr, uid, procurement.product_uom.id, procurement.product_qty, uom_id)
1089 if procurement.product_id.seller_ids[0].qty:
1090 qty=max(qty,procurement.product_id.seller_ids[0].qty)
1092 price = self.pool.get('product.pricelist').price_get(cr, uid, [pricelist_id], procurement.product_id.id, qty, False, {'uom': uom_id})[pricelist_id]
1094 newdate = DateTime.strptime(procurement.date_planned, '%Y-%m-%d %H:%M:%S')
1095 newdate = newdate - DateTime.RelativeDateTime(days=company.po_lead)
1096 newdate = newdate - procurement.product_id.seller_ids[0].delay
1098 context.update({'lang':partner.lang})
1099 product=self.pool.get('product.product').browse(cr,uid,procurement.product_id.id,context=context)
1102 'name': product.name,
1104 'product_id': procurement.product_id.id,
1105 'product_uom': uom_id,
1106 'price_unit': price,
1107 'date_planned': newdate.strftime('%Y-%m-%d %H:%M:%S'),
1108 'move_dest_id': res_id,
1109 'notes':product.description_purchase,
1112 taxes_ids = procurement.product_id.product_tmpl_id.supplier_taxes_id
1113 taxes = self.pool.get('account.fiscal.position').map_tax(cr, uid, partner.property_account_position, taxes_ids)
1115 'taxes_id':[(6,0,taxes)]
1117 purchase_id = self.pool.get('purchase.order').create(cr, uid, {
1118 'origin': procurement.origin,
1119 'partner_id': partner_id,
1120 'partner_address_id': address_id,
1121 'location_id': procurement.location_id.id,
1122 'pricelist_id': pricelist_id,
1123 'order_line': [(0,0,line)],
1124 'company_id': procurement.company_id.id,
1125 'fiscal_position': partner.property_account_position and partner.property_account_position.id or False
1127 self.write(cr, uid, [procurement.id], {'state':'running', 'purchase_id':purchase_id})
1130 def action_cancel(self, cr, uid, ids):
1133 for proc in self.browse(cr, uid, ids):
1135 if proc.move_id.state not in ('done','cancel'):
1136 todo2.append(proc.move_id.id)
1138 if proc.move_id and proc.move_id.state=='waiting':
1139 todo.append(proc.move_id.id)
1141 self.pool.get('stock.move').action_cancel(cr, uid, todo2)
1143 self.pool.get('stock.move').write(cr, uid, todo, {'state':'assigned'})
1144 self.write(cr, uid, ids, {'state':'cancel'})
1145 wf_service = netsvc.LocalService("workflow")
1147 wf_service.trg_trigger(uid, 'mrp.procurement', id, cr)
1150 def action_check_finnished(self, cr, uid, ids):
1151 return self.check_move_done(cr, uid, ids)
1153 def action_check(self, cr, uid, ids):
1155 for procurement in self.browse(cr, uid, ids):
1156 if procurement.move_id.state=='assigned' or procurement.move_id.state=='done':
1157 self.action_done(cr, uid, [procurement.id])
1161 def action_ready(self, cr, uid, ids):
1162 res = self.write(cr, uid, ids, {'state':'ready'})
1165 def action_done(self, cr, uid, ids):
1166 for procurement in self.browse(cr, uid, ids):
1167 if procurement.move_id:
1168 if procurement.close_move and (procurement.move_id.state <> 'done'):
1169 self.pool.get('stock.move').action_done(cr, uid, [procurement.move_id.id])
1170 res = self.write(cr, uid, ids, {'state':'done', 'date_close':time.strftime('%Y-%m-%d')})
1171 wf_service = netsvc.LocalService("workflow")
1173 wf_service.trg_trigger(uid, 'mrp.procurement', id, cr)
1176 def run_scheduler(self, cr, uid, automatic=False, use_new_cursor=False, context=None):
1178 use_new_cursor: False or the dbname
1182 self._procure_confirm(cr, uid, use_new_cursor=use_new_cursor, context=context)
1183 self._procure_orderpoint_confirm(cr, uid, automatic=automatic,\
1184 use_new_cursor=use_new_cursor, context=context)
1188 class stock_warehouse_orderpoint(osv.osv):
1189 _name = "stock.warehouse.orderpoint"
1190 _description = "Orderpoint minimum rule"
1192 'name': fields.char('Name', size=32, required=True),
1193 'active': fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the orderpoint without removing it."),
1194 'logic': fields.selection([('max','Order to Max'),('price','Best price (not yet active!)')], 'Reordering Mode', required=True),
1195 'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse', required=True),
1196 'location_id': fields.many2one('stock.location', 'Location', required=True),
1197 'product_id': fields.many2one('product.product', 'Product', required=True, domain=[('type','=','product')]),
1198 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True ),
1199 'product_min_qty': fields.float('Min Quantity', required=True,
1200 help="When the virtual stock goes belong the Min Quantity, Open ERP generates "\
1201 "a requisition to bring the virtual stock to the Max Quantity."),
1202 'product_max_qty': fields.float('Max 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 'qty_multiple': fields.integer('Qty Multiple', required=True,
1206 help="The requisition quantity will by rounded up to this multiple."),
1207 'procurement_id': fields.many2one('mrp.procurement', 'Purchase Order'),
1208 'company_id': fields.many2one('res.company','Company',required=True),
1211 'active': lambda *a: 1,
1212 'logic': lambda *a: 'max',
1213 'qty_multiple': lambda *a: 1,
1214 'name': lambda x,y,z,c: x.pool.get('ir.sequence').get(y,z,'mrp.warehouse.orderpoint') or '',
1215 'product_uom': lambda sel, cr, uid, context: context.get('product_uom', False),
1216 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'stock.warehouse.orderpoint', c)
1218 def onchange_warehouse_id(self, cr, uid, ids, warehouse_id, context={}):
1220 w=self.pool.get('stock.warehouse').browse(cr,uid,warehouse_id, context)
1221 v = {'location_id':w.lot_stock_id.id}
1224 def onchange_product_id(self, cr, uid, ids, product_id, context={}):
1226 prod=self.pool.get('product.product').browse(cr,uid,product_id)
1227 v = {'product_uom':prod.uom_id.id}
1230 def copy(self, cr, uid, id, default=None,context={}):
1234 'name': self.pool.get('ir.sequence').get(cr, uid, 'mrp.warehouse.orderpoint') or '',
1236 return super(stock_warehouse_orderpoint, self).copy(cr, uid, id, default, context)
1237 stock_warehouse_orderpoint()
1240 class StockMove(osv.osv):
1241 _inherit = 'stock.move'
1243 'procurements': fields.one2many('mrp.procurement', 'move_id', 'Requisitions'),
1245 def copy(self, cr, uid, id, default=None, context=None):
1246 default = default or {}
1247 default['procurements'] = []
1248 return super(StockMove, self).copy(cr, uid, id, default, context)
1250 def _action_explode(self, cr, uid, move, context={}):
1251 if move.product_id.supply_method=='produce' and move.product_id.procure_method=='make_to_order':
1252 bis = self.pool.get('mrp.bom').search(cr, uid, [
1253 ('product_id','=',move.product_id.id),
1254 ('bom_id','=',False),
1255 ('type','=','phantom')])
1257 factor = move.product_qty
1258 bom_point = self.pool.get('mrp.bom').browse(cr, uid, bis[0])
1259 res = self.pool.get('mrp.bom')._bom_explode(cr, uid, bom_point, factor, [])
1260 dest = move.product_id.product_tmpl_id.property_stock_production.id
1262 if move.state=='assigned':
1267 'picking_id': move.picking_id.id,
1268 'product_id': line['product_id'],
1269 'product_uom': line['product_uom'],
1270 'product_qty': line['product_qty'],
1271 'product_uos': line['product_uos'],
1272 'product_uos_qty': line['product_uos_qty'],
1273 'move_dest_id': move.id,
1275 'name': line['name'],
1276 'location_dest_id': dest,
1277 'move_history_ids': [(6,0,[move.id])],
1278 'move_history_ids2': [(6,0,[])],
1281 mid = self.pool.get('stock.move').copy(cr, uid, move.id, default=valdef)
1282 prodobj = self.pool.get('product.product').browse(cr, uid, line['product_id'], context=context)
1283 proc_id = self.pool.get('mrp.procurement').create(cr, uid, {
1284 'name': (move.picking_id.origin or ''),
1285 'origin': (move.picking_id.origin or ''),
1286 'date_planned': move.date_planned,
1287 'product_id': line['product_id'],
1288 'product_qty': line['product_qty'],
1289 'product_uom': line['product_uom'],
1290 'product_uos_qty': line['product_uos'] and line['product_uos_qty'] or False,
1291 'product_uos': line['product_uos'],
1292 'location_id': move.location_id.id,
1293 'procure_method': prodobj.procure_method,
1295 'company_id': line['company_id'],
1297 wf_service = netsvc.LocalService("workflow")
1298 wf_service.trg_validate(uid, 'mrp.procurement', proc_id, 'button_confirm', cr)
1299 self.pool.get('stock.move').write(cr, uid, [move.id], {
1300 'location_id': move.location_dest_id.id,
1301 'auto_validate': True,
1302 'picking_id': False,
1303 'location_id': dest,
1306 for m in self.pool.get('mrp.procurement').search(cr, uid, [('move_id','=',move.id)], context):
1307 wf_service = netsvc.LocalService("workflow")
1308 wf_service.trg_validate(uid, 'mrp.procurement', m, 'button_wait_done', cr)
1313 class StockPicking(osv.osv):
1314 _inherit = 'stock.picking'
1316 def test_finnished(self, cursor, user, ids):
1317 wf_service = netsvc.LocalService("workflow")
1318 res = super(StockPicking, self).test_finnished(cursor, user, ids)
1319 for picking in self.browse(cursor, user, ids):
1320 for move in picking.move_lines:
1321 if move.state == 'done' and move.procurements:
1322 for procurement in move.procurements:
1323 wf_service.trg_validate(user, 'mrp.procurement',
1324 procurement.id, 'button_check', cursor)
1328 # Explode picking by replacing phantom BoMs
1330 def action_explode(self, cr, uid, picks, *args):
1331 for move in self.pool.get('stock.move').browse(cr, uid, picks):
1332 self.pool.get('stock.move')._action_explode(cr, uid, move)
1337 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: