1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2010 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 mx import DateTime
23 from osv import fields
25 from tools.translate import _
30 #----------------------------------------------------------
32 #----------------------------------------------------------
33 # capacity_hour : capacity per hour. default: 1.0.
34 # Eg: If 5 concurrent operations at one time: capacity = 5 (because 5 employees)
35 # unit_per_cycle : how many units are produced for one cycle
37 # TODO: Work Center may be recursive ?
39 class mrp_workcenter(osv.osv):
40 _name = 'mrp.workcenter'
41 _description = 'Work Center'
42 _inherits = {'resource.resource':"resource_id"}
44 # 'name': fields.char('Work Center Name', size=64, required=True),
45 'note': fields.text('Description', help="Description of the workcenter. Explain here what's a cycle according to this workcenter."),
46 '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."),
47 'time_cycle': fields.float('Time for 1 cycle (hour)', help="Time in hours for doing one cycle."),
48 'time_start': fields.float('Time before prod.', help="Time in hours for the setup."),
49 'time_stop': fields.float('Time after prod.', help="Time in hours for the cleaning."),
50 'costs_hour': fields.float('Cost per hour'),
51 'costs_hour_account_id': fields.many2one('account.analytic.account', 'Hour Account', domain=[('type','<>','view')],
52 help="Complete this only if you want automatic analytic accounting entries on production orders."),
53 'costs_cycle': fields.float('Cost per cycle'),
54 'costs_cycle_account_id': fields.many2one('account.analytic.account', 'Cycle Account', domain=[('type','<>','view')],
55 help="Complete this only if you want automatic analytic accounting entries on production orders."),
56 'costs_journal_id': fields.many2one('account.analytic.journal', 'Analytic Journal'),
57 'costs_general_account_id': fields.many2one('account.account', 'General Account', domain=[('type','<>','view')]),
58 # 'company_id': fields.many2one('res.company','Company',required=True),
59 'resource_id': fields.many2one('resource.resource','Resource',ondelete='cascade'),
62 'capacity_per_cycle': lambda *a: 1.0,
67 class mrp_property_group(osv.osv):
68 _name = 'mrp.property.group'
69 _description = 'Property Group'
71 'name': fields.char('Property Group', size=64, required=True),
72 'description': fields.text('Description'),
76 class mrp_property(osv.osv):
77 _name = 'mrp.property'
78 _description = 'Property'
80 'name': fields.char('Name', size=64, required=True),
81 'composition': fields.selection([('min','min'),('max','max'),('plus','plus')], 'Properties composition', required=True, help="Not used in computations, for information purpose only."),
82 'group_id': fields.many2one('mrp.property.group', 'Property Group', required=True),
83 'description': fields.text('Description'),
86 'composition': lambda *a: 'min',
90 class mrp_routing(osv.osv):
92 _description = 'Routing'
94 'name': fields.char('Name', size=64, required=True),
95 'active': fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the routing without removing it."),
96 'code': fields.char('Code', size=8),
98 'note': fields.text('Description'),
99 'workcenter_lines': fields.one2many('mrp.routing.workcenter', 'routing_id', 'Work Centers'),
101 'location_id': fields.many2one('stock.location', 'Production Location',
102 help="Keep empty if you produce at the location where the finished products are needed." \
103 "Set a location if you produce at a fixed location. This can be a partner location " \
104 "if you subcontract the manufacturing operations."
108 'active': lambda *a: 1,
112 class mrp_routing_workcenter(osv.osv):
113 _name = 'mrp.routing.workcenter'
114 _description = 'Routing workcenter usage'
116 'workcenter_id': fields.many2one('mrp.workcenter', 'Work Center', required=True),
117 'name': fields.char('Name', size=64, required=True),
118 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of routing workcenters."),
119 'cycle_nbr': fields.float('Number of Cycles', required=True,
120 help="Time in hours for doing one cycle."),
121 'hour_nbr': fields.float('Number of Hours', required=True, help="Cost per hour"),
122 'routing_id': fields.many2one('mrp.routing', 'Parent Routing', select=True, ondelete='cascade',
123 help="Routing indicates all the workcenters used, for how long and/or cycles." \
124 "If Routing is indicated then,the third tab of a production order (workcenters) will be automatically pre-completed."),
125 'note': fields.text('Description')
128 'cycle_nbr': lambda *a: 1.0,
129 'hour_nbr': lambda *a: 0.0,
131 mrp_routing_workcenter()
133 class mrp_bom(osv.osv):
135 _description = 'Bills of Material'
136 def _child_compute(self, cr, uid, ids, name, arg, context={}):
138 for bom in self.browse(cr, uid, ids, context=context):
139 result[bom.id] = map(lambda x: x.id, bom.bom_lines)
142 ok = ((name=='child_complete_ids') and (bom.product_id.supply_method=='produce'))
143 if bom.type=='phantom' or ok:
144 sids = self.pool.get('mrp.bom').search(cr, uid, [('bom_id','=',False),('product_id','=',bom.product_id.id)])
146 bom2 = self.pool.get('mrp.bom').browse(cr, uid, sids[0], context=context)
147 result[bom.id] += map(lambda x: x.id, bom2.bom_lines)
149 def _compute_type(self, cr, uid, ids, field_name, arg, context):
150 res = dict(map(lambda x: (x,''), ids))
151 for line in self.browse(cr, uid, ids):
152 if line.type=='phantom' and not line.bom_id:
155 if line.bom_lines or line.type=='phantom':
157 if line.product_id.supply_method=='produce':
158 if line.product_id.procure_method=='make_to_stock':
159 res[line.id] = 'stock'
161 res[line.id] = 'order'
164 'name': fields.char('Name', size=64, required=True),
165 'code': fields.char('Code', size=16),
166 '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."),
167 'type': fields.selection([('normal','Normal BoM'),('phantom','Sets / Phantom')], 'BoM Type', required=True,
168 help= "If a sub-product is used in several products, it can be useful to create its own BoM."\
169 "Though if you don't want separated production orders for this sub-product, select Set/Phantom as BoM type."\
170 "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."),
171 'method': fields.function(_compute_type, string='Method', method=True, type='selection', selection=[('',''),('stock','On Stock'),('order','On Order'),('set','Set / Pack')]),
172 'date_start': fields.date('Valid From', help="Validity of this BoM or component. Keep empty if it's always valid."),
173 'date_stop': fields.date('Valid Until', help="Validity of this BoM or component. Keep empty if it's always valid."),
174 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of bills of material."),
175 'position': fields.char('Internal Reference', size=64, help="Reference to a position in an external plan."),
176 'product_id': fields.many2one('product.product', 'Product', required=True),
177 'product_uos_qty': fields.float('Product UOS Qty'),
178 '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."),
179 'product_qty': fields.float('Product Qty', required=True),
180 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True, help="UoM (Unit of Measure) is the unit of measurement for the inventory control"),
181 'product_rounding': fields.float('Product Rounding', help="Rounding applied on the product quantity."),
182 'product_efficiency': fields.float('Product Efficiency', required=True, help="Material efficiency. A factor of 0.9 means a loss of 10% in the production."),
183 'bom_lines': fields.one2many('mrp.bom', 'bom_id', 'BoM Lines'),
184 'bom_id': fields.many2one('mrp.bom', 'Parent BoM', ondelete='cascade', select=True),
185 '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."),
186 'property_ids': fields.many2many('mrp.property', 'mrp_bom_property_rel', 'bom_id','property_id', 'Properties'),
187 'revision_ids': fields.one2many('mrp.bom.revision', 'bom_id', 'BoM Revisions'),
188 'revision_type': fields.selection([('numeric','numeric indices'),('alpha','alphabetical indices')], 'Index type'),
189 'child_ids': fields.function(_child_compute,relation='mrp.bom', method=True, string="BoM Hierarchy", type='many2many'),
190 'child_complete_ids': fields.function(_child_compute,relation='mrp.bom', method=True, string="BoM Hierarchy", type='many2many'),
191 'company_id': fields.many2one('res.company','Company',required=True),
194 'active': lambda *a: 1,
195 'product_efficiency': lambda *a: 1.0,
196 'product_qty': lambda *a: 1.0,
197 'product_rounding': lambda *a: 1.0,
198 'type': lambda *a: 'normal',
199 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'mrp.bom', context=c)
203 ('bom_qty_zero', 'CHECK (product_qty>0)', 'All product quantities must be greater than 0.\n' \
204 'You should install the mrp_subproduct module if you want to manage extra products on BoMs !'),
207 def _check_recursion(self, cr, uid, ids):
210 cr.execute('select distinct bom_id from mrp_bom where id =ANY(%s)',(ids,))
211 ids = filter(None, map(lambda x:x[0], cr.fetchall()))
217 (_check_recursion, 'Error ! You can not create recursive BoM.', ['parent_id'])
221 def onchange_product_id(self, cr, uid, ids, product_id, name, context={}):
223 prod=self.pool.get('product.product').browse(cr,uid,[product_id])[0]
224 v = {'product_uom':prod.uom_id.id}
226 v['name'] = prod.name
230 def _bom_find(self, cr, uid, product_id, product_uom, properties=[]):
232 # Why searching on BoM without parent ?
233 cr.execute('select id from mrp_bom where product_id=%s and bom_id is null order by sequence', (product_id,))
234 ids = map(lambda x: x[0], cr.fetchall())
237 for bom in self.pool.get('mrp.bom').browse(cr, uid, ids):
239 for prop_id in bom.property_ids:
240 if prop_id.id in properties:
242 if (prop>max_prop) or ((max_prop==0) and not result):
247 def _bom_explode(self, cr, uid, bom, factor, properties, addthis=False, level=0):
248 factor = factor / (bom.product_efficiency or 1.0)
249 factor = rounding(factor, bom.product_rounding)
250 if factor<bom.product_rounding:
251 factor = bom.product_rounding
255 if bom.type=='phantom' and not bom.bom_lines:
256 newbom = self._bom_find(cr, uid, bom.product_id.id, bom.product_uom.id, properties)
258 res = self._bom_explode(cr, uid, self.browse(cr, uid, [newbom])[0], factor*bom.product_qty, properties, addthis=True, level=level+10)
259 result = result + res[0]
260 result2 = result2 + res[1]
265 if addthis and not bom.bom_lines:
268 'name': bom.product_id.name,
269 'product_id': bom.product_id.id,
270 'product_qty': bom.product_qty * factor,
271 'product_uom': bom.product_uom.id,
272 'product_uos_qty': bom.product_uos and bom.product_uos_qty * factor or False,
273 'product_uos': bom.product_uos and bom.product_uos.id or False,
276 for wc_use in bom.routing_id.workcenter_lines:
277 wc = wc_use.workcenter_id
278 d, m = divmod(factor, wc_use.workcenter_id.capacity_per_cycle)
279 mult = (d + (m and 1.0 or 0.0))
280 cycle = mult * wc_use.cycle_nbr
282 'name': bom.routing_id.name,
283 'workcenter_id': wc.id,
284 'sequence': level+(wc_use.sequence or 0),
286 'hour': float(wc_use.hour_nbr*mult + (wc.time_start+wc.time_stop+cycle*wc.time_cycle) * (wc.time_efficiency or 1.0)),
288 for bom2 in bom.bom_lines:
289 res = self._bom_explode(cr, uid, bom2, factor, properties, addthis=True, level=level+10)
290 result = result + res[0]
291 result2 = result2 + res[1]
292 return result, result2
294 def set_indices(self, cr, uid, ids, context = {}):
295 if not ids or (ids and not ids[0]):
297 res = self.read(cr, uid, ids, ['revision_ids', 'revision_type'])
298 rev_ids = res[0]['revision_ids']
301 for rev_id in rev_ids:
302 if res[0]['revision_type'] == 'numeric':
303 self.pool.get('mrp.bom.revision').write(cr, uid, [rev_id], {'indice' : idx})
305 self.pool.get('mrp.bom.revision').write(cr, uid, [rev_id], {'indice' : "%c"%(idx+96,)})
311 class mrp_bom_revision(osv.osv):
312 _name = 'mrp.bom.revision'
313 _description = 'Bill of material revisions'
315 'name': fields.char('Modification name', size=64, required=True),
316 'description': fields.text('Description'),
317 'date': fields.date('Modification Date'),
318 'indice': fields.char('Revision', size=16),
319 'last_indice': fields.char('last indice', size=64),
320 'author_id': fields.many2one('res.users', 'Author'),
321 'bom_id': fields.many2one('mrp.bom', 'BoM', select=True),
325 'author_id': lambda x,y,z,c: z,
326 'date': lambda *a: time.strftime('%Y-%m-%d'),
334 return round(f / r) * r
336 class many2many_domain(fields.many2many):
337 def set(self, cr, obj, id, name, values, user=None, context=None):
340 return super(many2many_domain, self).set(cr, obj, id, name, values, user=user,
343 def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
347 move_obj = obj.pool.get('stock.move')
348 for prod in obj.browse(cr, user, ids, context=context):
349 cr.execute("SELECT move_id from mrp_production_move_ids where\
350 production_id=%s" % (prod.id))
351 m_ids = map(lambda x: x[0], cr.fetchall())
352 final = move_obj.search(cr, user, self._domain + [('id', 'in', tuple(m_ids))])
356 class one2many_domain(fields.one2many):
357 def set(self, cr, obj, id, field, values, user=None, context=None):
360 return super(one2many_domain, self).set(cr, obj, id, field, values,
361 user=user, context=context)
363 def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
367 move_obj = obj.pool.get('stock.move')
368 for prod in obj.browse(cr, user, ids, context=context):
369 cr.execute("SELECT id from stock_move where production_id=%s" % (prod.id))
370 m_ids = map(lambda x: x[0], cr.fetchall())
371 final = move_obj.search(cr, user, self._domain + [('id', 'in', tuple(m_ids))])
375 class mrp_production(osv.osv):
376 _name = 'mrp.production'
377 _description = 'Production'
378 _date_name = 'date_planned'
380 def _get_sale_order(self,cr,uid,ids,field_name=False):
381 move_obj=self.pool.get('stock.move')
382 def get_parent_move(move_id):
383 move = move_obj.browse(cr,uid,move_id)
384 if move.move_dest_id:
385 return get_parent_move(move.move_dest_id.id)
387 productions=self.read(cr,uid,ids,['id','move_prod_id'])
389 for production in productions:
390 res[production['id']]=False
391 if production.get('move_prod_id',False):
392 parent_move_line=get_parent_move(production['move_prod_id'][0])
394 move = move_obj.browse(cr,uid,parent_move_line)
395 #TODO: fix me sale module can not be used here,
396 #as may be mrp can be installed without sale module
397 if field_name=='name':
398 res[production['id']]=move.sale_line_id and move.sale_line_id.order_id.name or False
399 if field_name=='client_order_ref':
400 res[production['id']]=move.sale_line_id and move.sale_line_id.order_id.client_order_ref or False
403 def _production_calc(self, cr, uid, ids, prop, unknow_none, context={}):
405 for prod in self.browse(cr, uid, ids, context=context):
410 for wc in prod.workcenter_lines:
411 result[prod.id]['hour_total'] += wc.hour
412 result[prod.id]['cycle_total'] += wc.cycle
415 def _production_date_end(self, cr, uid, ids, prop, unknow_none, context={}):
417 for prod in self.browse(cr, uid, ids, context=context):
418 result[prod.id] = prod.date_planned
421 def _production_date(self, cr, uid, ids, prop, unknow_none, context={}):
423 for prod in self.browse(cr, uid, ids, context=context):
424 result[prod.id] = prod.date_planned[:10]
427 def _sale_name_calc(self, cr, uid, ids, prop, unknow_none, unknow_dict):
428 return self._get_sale_order(cr,uid,ids,field_name='name')
430 def _sale_ref_calc(self, cr, uid, ids, prop, unknow_none, unknow_dict):
431 return self._get_sale_order(cr,uid,ids,field_name='client_order_ref')
434 'name': fields.char('Reference', size=64, required=True),
435 'origin': fields.char('Source Document', size=64, help="Reference of the document that generated this production order request."),
436 'priority': fields.selection([('0','Not urgent'),('1','Normal'),('2','Urgent'),('3','Very Urgent')], 'Priority'),
438 'product_id': fields.many2one('product.product', 'Product', required=True, domain=[('type','<>','service')]),
439 'product_qty': fields.float('Product Qty', required=True, states={'draft':[('readonly',False)]}, readonly=True),
440 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True, states={'draft':[('readonly',False)]}, readonly=True),
441 'product_uos_qty': fields.float('Product UoS Qty', states={'draft':[('readonly',False)]}, readonly=True),
442 'product_uos': fields.many2one('product.uom', 'Product UoS', states={'draft':[('readonly',False)]}, readonly=True),
444 'location_src_id': fields.many2one('stock.location', 'Raw Materials Location', required=True,
445 help="Location where the system will look for components."),
446 'location_dest_id': fields.many2one('stock.location', 'Finished Products Location', required=True,
447 help="Location where the system will stock the finished products."),
449 'date_planned_end': fields.function(_production_date_end, method=True, type='date', string='Scheduled End'),
450 'date_planned_date': fields.function(_production_date, method=True, type='date', string='Scheduled Date'),
451 'date_planned': fields.datetime('Scheduled date', required=True, select=1),
452 'date_start': fields.datetime('Start Date'),
453 'date_finnished': fields.datetime('End Date'),
455 'bom_id': fields.many2one('mrp.bom', 'Bill of Material', domain=[('bom_id','=',False)]),
456 '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."),
458 'picking_id': fields.many2one('stock.picking', 'Picking list', readonly=True,
459 help="This is the internal picking list that brings the finished product to the production plan"),
460 'move_prod_id': fields.many2one('stock.move', 'Move product', readonly=True),
461 'move_lines': many2many_domain('stock.move', 'mrp_production_move_ids', 'production_id', 'move_id', 'Products to Consumme', domain=[('state','not in', ('done', 'cancel'))]),
462 'move_lines2': many2many_domain('stock.move', 'mrp_production_move_ids', 'production_id', 'move_id', 'Consummed Products', domain=[('state','in', ('done', 'cancel'))]),
463 'move_created_ids': one2many_domain('stock.move', 'production_id', 'Moves Created', domain=[('state','not in', ('done', 'cancel'))]),
464 'move_created_ids2': one2many_domain('stock.move', 'production_id', 'Moves Created', domain=[('state','in', ('done', 'cancel'))]),
465 'product_lines': fields.one2many('mrp.production.product.line', 'production_id', 'Scheduled goods'),
466 'workcenter_lines': fields.one2many('mrp.production.workcenter.line', 'production_id', 'Work Centers Utilisation'),
467 '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,
468 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 \'Picking Exception\'.\
469 \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\'.'),
470 'hour_total': fields.function(_production_calc, method=True, type='float', string='Total Hours', multi='workorder'),
471 'cycle_total': fields.function(_production_calc, method=True, type='float', string='Total Cycles', multi='workorder'),
473 'sale_name': fields.function(_sale_name_calc, method=True, type='char', string='Sale Name', help='Indicate the name of sale order.'),
474 'sale_ref': fields.function(_sale_ref_calc, method=True, type='char', string='Sale Reference', help='Indicate the Customer Reference from sale order.'),
475 'company_id': fields.many2one('res.company','Company',required=True),
478 'priority': lambda *a: '1',
479 'state': lambda *a: 'draft',
480 'date_planned': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
481 'product_qty': lambda *a: 1.0,
482 'name': lambda x,y,z,c: x.pool.get('ir.sequence').get(y,z,'mrp.production') or '/',
483 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'mrp.production', context=c),
485 _order = 'date_planned asc, priority desc';
486 def unlink(self, cr, uid, ids, context=None):
487 productions = self.read(cr, uid, ids, ['state'])
489 for s in productions:
490 if s['state'] in ['draft','cancel']:
491 unlink_ids.append(s['id'])
493 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Production Order(s) which are in %s State!' % s['state']))
494 return osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
496 def copy(self, cr, uid, id, default=None,context=None):
500 'name': self.pool.get('ir.sequence').get(cr, uid, 'mrp.production'),
502 'move_created_ids': [],
505 return super(mrp_production, self).copy(cr, uid, id, default, context)
507 def location_id_change(self, cr, uid, ids, src, dest, context={}):
511 return {'value': {'location_dest_id': src}}
514 def product_id_change(self, cr, uid, ids, product):
517 res = self.pool.get('product.product').read(cr, uid, [product], ['uom_id'])[0]
518 uom = res['uom_id'] and res['uom_id'][0]
519 result = {'product_uom':uom}
520 return {'value':result}
522 def bom_id_change(self, cr, uid, ids, product):
525 res = self.pool.get('mrp.bom').read(cr, uid, [product], ['routing_id'])[0]
526 routing_id = res['routing_id'] and res['routing_id'][0]
527 result = {'routing_id':routing_id}
528 return {'value':result}
530 def action_picking_except(self, cr, uid, ids):
531 self.write(cr, uid, ids, {'state':'picking_except'})
534 def action_compute(self, cr, uid, ids, properties=[]):
536 for production in self.browse(cr, uid, ids):
537 cr.execute('delete from mrp_production_product_line where production_id=%s', (production.id,))
538 cr.execute('delete from mrp_production_workcenter_line where production_id=%s', (production.id,))
539 bom_point = production.bom_id
540 bom_id = production.bom_id.id
542 bom_id = self.pool.get('mrp.bom')._bom_find(cr, uid, production.product_id.id, production.product_uom.id, properties)
544 bom_point = self.pool.get('mrp.bom').browse(cr, uid, bom_id)
545 routing_id = bom_point.routing_id.id or False
546 self.write(cr, uid, [production.id], {'bom_id': bom_id, 'routing_id': routing_id})
549 raise osv.except_osv(_('Error'), _("Couldn't find bill of material for product"))
551 #if bom_point.routing_id and bom_point.routing_id.location_id:
552 # self.write(cr, uid, [production.id], {'location_src_id': bom_point.routing_id.location_id.id})
554 factor = production.product_qty * production.product_uom.factor / bom_point.product_uom.factor
555 res = self.pool.get('mrp.bom')._bom_explode(cr, uid, bom_point, factor / bom_point.product_qty, properties)
559 line['production_id'] = production.id
560 self.pool.get('mrp.production.product.line').create(cr, uid, line)
561 for line in results2:
562 line['production_id'] = production.id
563 self.pool.get('mrp.production.workcenter.line').create(cr, uid, line)
566 def action_cancel(self, cr, uid, ids):
567 for production in self.browse(cr, uid, ids):
568 if production.move_created_ids:
569 self.pool.get('stock.move').action_cancel(cr, uid, [x.id for x in production.move_created_ids])
570 self.pool.get('stock.move').action_cancel(cr, uid, [x.id for x in production.move_lines])
571 self.write(cr, uid, ids, {'state':'cancel'}) #,'move_lines':[(6,0,[])]})
574 #XXX: may be a bug here; lot_lines are unreserved for a few seconds;
575 # between the end of the picking list and the call to this function
576 def action_ready(self, cr, uid, ids):
577 self.write(cr, uid, ids, {'state':'ready'})
578 for production in self.browse(cr, uid, ids):
579 if production.move_prod_id:
580 self.pool.get('stock.move').write(cr, uid, [production.move_prod_id.id],
581 {'location_id':production.location_dest_id.id})
584 #TODO Review materials in function in_prod and prod_end.
585 def action_production_end(self, cr, uid, ids):
587 for production in self.browse(cr, uid, ids):
588 for res in production.move_lines:
589 for move in production.move_created_ids:
590 #XXX must use the orm
591 cr.execute('INSERT INTO stock_move_history_ids \
592 (parent_id, child_id) VALUES (%s,%s)',
594 move_ids.append(res.id)
595 vals= {'state':'confirmed'}
596 new_moves = [x.id for x in production.move_created_ids]
597 self.pool.get('stock.move').write(cr, uid, new_moves, vals)
598 if not production.date_finnished:
599 self.write(cr, uid, [production.id],
600 {'date_finnished': time.strftime('%Y-%m-%d %H:%M:%S')})
601 self.pool.get('stock.move').check_assign(cr, uid, new_moves)
602 self.pool.get('stock.move').action_done(cr, uid, new_moves)
603 self._costs_generate(cr, uid, production)
604 self.pool.get('stock.move').action_done(cr, uid, move_ids)
605 self.write(cr, uid, ids, {'state': 'done'})
608 def _costs_generate(self, cr, uid, production):
610 for wc_line in production.workcenter_lines:
611 wc = wc_line.workcenter_id
612 if wc.costs_journal_id and wc.costs_general_account_id:
613 value = wc_line.hour * wc.costs_hour
614 account = wc.costs_hour_account_id.id
615 if value and account:
617 self.pool.get('account.analytic.line').create(cr, uid, {
618 'name': wc_line.name+' (H)',
620 'account_id': account,
621 'general_account_id': wc.costs_general_account_id.id,
622 'journal_id': wc.costs_journal_id.id,
625 if wc.costs_journal_id and wc.costs_general_account_id:
626 value = wc_line.cycle * wc.costs_cycle
627 account = wc.costs_cycle_account_id.id
628 if value and account:
630 self.pool.get('account.analytic.line').create(cr, uid, {
631 'name': wc_line.name+' (C)',
633 'account_id': account,
634 'general_account_id': wc.costs_general_account_id.id,
635 'journal_id': wc.costs_journal_id.id,
640 def action_in_production(self, cr, uid, ids):
642 self.write(cr, uid, ids, {'state': 'in_production'})
645 def test_if_product(self, cr, uid, ids):
647 for production in self.browse(cr, uid, ids):
648 if not production.product_lines:
649 if not self.action_compute(cr, uid, [production.id]):
653 def _get_auto_picking(self, cr, uid, production):
656 def action_confirm(self, cr, uid, ids):
659 for production in self.browse(cr, uid, ids):
660 if not production.product_lines:
661 self.action_compute(cr, uid, [production.id])
662 production = self.browse(cr, uid, [production.id])[0]
664 pick_type = 'internal'
666 if production.bom_id.routing_id and production.bom_id.routing_id.location_id:
667 routing_loc = production.bom_id.routing_id.location_id
668 if routing_loc.usage<>'internal':
670 address_id = routing_loc.address_id and routing_loc.address_id.id or False
671 routing_loc = routing_loc.id
672 picking_id = self.pool.get('stock.picking').create(cr, uid, {
673 'origin': (production.origin or '').split(':')[0] +':'+production.name,
677 'address_id': address_id,
678 'auto_picking': self._get_auto_picking(cr, uid, production),
679 'company_id': production.company_id.id,
682 source = production.product_id.product_tmpl_id.property_stock_production.id
684 'name':'PROD:'+production.name,
685 'date_planned': production.date_planned,
686 'product_id': production.product_id.id,
687 'product_qty': production.product_qty,
688 'product_uom': production.product_uom.id,
689 'product_uos_qty': production.product_uos and production.product_uos_qty or False,
690 'product_uos': production.product_uos and production.product_uos.id or False,
691 'location_id': source,
692 'location_dest_id': production.location_dest_id.id,
693 'move_dest_id': production.move_prod_id.id,
695 'company_id': production.company_id.id,
697 res_final_id = self.pool.get('stock.move').create(cr, uid, data)
699 self.write(cr, uid, [production.id], {'move_created_ids': [(6, 0, [res_final_id])]})
701 for line in production.product_lines:
703 newdate = production.date_planned
704 if line.product_id.type in ('product', 'consu'):
705 res_dest_id = self.pool.get('stock.move').create(cr, uid, {
706 'name':'PROD:'+production.name,
707 'date_planned': production.date_planned,
708 'product_id': line.product_id.id,
709 'product_qty': line.product_qty,
710 'product_uom': line.product_uom.id,
711 'product_uos_qty': line.product_uos and line.product_uos_qty or False,
712 'product_uos': line.product_uos and line.product_uos.id or False,
713 'location_id': routing_loc or production.location_src_id.id,
714 'location_dest_id': source,
715 'move_dest_id': res_final_id,
717 'company_id': production.company_id.id,
719 moves.append(res_dest_id)
720 move_id = self.pool.get('stock.move').create(cr, uid, {
721 'name':'PROD:'+production.name,
722 'picking_id':picking_id,
723 'product_id': line.product_id.id,
724 'product_qty': line.product_qty,
725 'product_uom': line.product_uom.id,
726 'product_uos_qty': line.product_uos and line.product_uos_qty or False,
727 'product_uos': line.product_uos and line.product_uos.id or False,
728 'date_planned': newdate,
729 'move_dest_id': res_dest_id,
730 'location_id': production.location_src_id.id,
731 'location_dest_id': routing_loc or production.location_src_id.id,
733 'company_id': production.company_id.id,
735 proc_id = self.pool.get('mrp.procurement').create(cr, uid, {
736 'name': (production.origin or '').split(':')[0] + ':' + production.name,
737 'origin': (production.origin or '').split(':')[0] + ':' + production.name,
738 'date_planned': newdate,
739 'product_id': line.product_id.id,
740 'product_qty': line.product_qty,
741 'product_uom': line.product_uom.id,
742 'product_uos_qty': line.product_uos and line.product_qty or False,
743 'product_uos': line.product_uos and line.product_uos.id or False,
744 'location_id': production.location_src_id.id,
745 'procure_method': line.product_id.procure_method,
747 'company_id': production.company_id.id,
749 wf_service = netsvc.LocalService("workflow")
750 wf_service.trg_validate(uid, 'mrp.procurement', proc_id, 'button_confirm', cr)
751 proc_ids.append(proc_id)
752 wf_service = netsvc.LocalService("workflow")
753 wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
754 self.write(cr, uid, [production.id], {'picking_id':picking_id, 'move_lines': [(6,0,moves)], 'state':'confirmed'})
757 def force_production(self, cr, uid, ids, *args):
758 pick_obj = self.pool.get('stock.picking')
759 pick_obj.force_assign(cr, uid, [prod.picking_id.id for prod in self.browse(cr, uid, ids)])
765 class stock_move(osv.osv):
767 _inherit = 'stock.move'
769 'production_id': fields.many2one('mrp.production', 'Production', select=True),
773 class mrp_production_workcenter_line(osv.osv):
774 _name = 'mrp.production.workcenter.line'
775 _description = 'Work Orders'
778 'name': fields.char('Work Order', size=64, required=True),
779 'workcenter_id': fields.many2one('mrp.workcenter', 'Work Center', required=True),
780 'cycle': fields.float('Nbr of cycles', digits=(16,2)),
781 'hour': fields.float('Nbr of hours', digits=(16,2)),
782 'sequence': fields.integer('Sequence', required=True, help="Gives the sequence order when displaying a list of work orders."),
783 'production_id': fields.many2one('mrp.production', 'Production Order', select=True, ondelete='cascade'),
786 'sequence': lambda *a: 1,
787 'hour': lambda *a: 0,
788 'cycle': lambda *a: 0,
790 mrp_production_workcenter_line()
792 class mrp_production_product_line(osv.osv):
793 _name = 'mrp.production.product.line'
794 _description = 'Production scheduled products'
796 'name': fields.char('Name', size=64, required=True),
797 'product_id': fields.many2one('product.product', 'Product', required=True),
798 'product_qty': fields.float('Product Qty', required=True),
799 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True),
800 'product_uos_qty': fields.float('Product UOS Qty'),
801 'product_uos': fields.many2one('product.uom', 'Product UOS'),
802 'production_id': fields.many2one('mrp.production', 'Production Order', select=True),
804 mrp_production_product_line()
806 # ------------------------------------------------------------------
808 # ------------------------------------------------------------------
810 # Produce, Buy or Find products and place a move
811 # then wizard for picking lists & move
813 class mrp_procurement(osv.osv):
814 _name = "mrp.procurement"
815 _description = "Procurement"
816 _order = 'priority,date_planned'
818 'name': fields.char('Reason', size=64, required=True, help='Requisition name.'),
819 'origin': fields.char('Source Document', size=64,
820 help="Reference of the document that created this Requisition.\n"
821 "This is automatically completed by Open ERP."),
822 'priority': fields.selection([('0','Not urgent'),('1','Normal'),('2','Urgent'),('3','Very Urgent')], 'Priority', required=True),
823 'date_planned': fields.datetime('Scheduled date', required=True),
824 'date_close': fields.datetime('Date Closed'),
825 'product_id': fields.many2one('product.product', 'Product', required=True, states={'draft':[('readonly',False)]}, readonly=True),
826 'product_qty': fields.float('Quantity', required=True, states={'draft':[('readonly',False)]}, readonly=True),
827 'product_uom': fields.many2one('product.uom', 'Product UoM', required=True, states={'draft':[('readonly',False)]}, readonly=True),
828 'product_uos_qty': fields.float('UoS Quantity', states={'draft':[('readonly',False)]}, readonly=True),
829 'product_uos': fields.many2one('product.uom', 'Product UoS', states={'draft':[('readonly',False)]}, readonly=True),
830 'move_id': fields.many2one('stock.move', 'Reservation', ondelete='set null'),
832 'bom_id': fields.many2one('mrp.bom', 'BoM', ondelete='cascade', select=True),
834 'close_move': fields.boolean('Close Move at end', required=True),
835 'location_id': fields.many2one('stock.location', 'Location', required=True, states={'draft':[('readonly',False)]}, readonly=True),
836 'procure_method': fields.selection([('make_to_stock','from stock'),('make_to_order','on order')], 'Procurement Method', states={'draft':[('readonly',False)], 'confirmed':[('readonly',False)]},
837 readonly=True, required=True, help="If you encode manually a Procurement, you probably want to use" \
838 " a make to order method."),
840 'purchase_id': fields.many2one('purchase.order', 'Purchase Order'),
841 'note': fields.text('Note'),
843 'property_ids': fields.many2many('mrp.property', 'mrp_procurement_property_rel', 'procurement_id','property_id', 'Properties'),
845 'message': fields.char('Latest error', size=64, help="Exception occurred while computing procurement orders."),
846 'state': fields.selection([
848 ('confirmed','Confirmed'),
849 ('exception','Exception'),
850 ('running','Running'),
854 ('waiting','Waiting')], 'State', required=True,
855 help='When a procurement is created the state is set to \'Draft\'.\n If the procurement is confirmed, the state is set to \'Confirmed\'.\
856 \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.'),
857 'note' : fields.text('Note'),
858 'company_id': fields.many2one('res.company','Company',required=True),
861 'state': lambda *a: 'draft',
862 'priority': lambda *a: '1',
863 'date_planned': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
864 'close_move': lambda *a: 0,
865 'procure_method': lambda *a: 'make_to_order',
866 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'mrp.procurement', context=c)
869 def unlink(self, cr, uid, ids, context=None):
870 procurements = self.read(cr, uid, ids, ['state'])
872 for s in procurements:
873 if s['state'] in ['draft','cancel']:
874 unlink_ids.append(s['id'])
876 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Requisition Order(s) which are in %s State!' % s['state']))
877 return osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
879 def onchange_product_id(self, cr, uid, ids, product_id, context={}):
881 w=self.pool.get('product.product').browse(cr,uid,product_id, context)
883 'product_uom':w.uom_id.id,
884 'product_uos':w.uos_id and w.uos_id.id or w.uom_id.id
889 def check_product(self, cr, uid, ids):
890 for procurement in self.browse(cr, uid, ids):
891 if procurement.product_id.type in ('product', 'consu'):
895 def check_move_cancel(self, cr, uid, ids, context={}):
898 for procurement in self.browse(cr, uid, ids, context):
899 if procurement.move_id:
901 if not procurement.move_id.state=='cancel':
905 def check_move_done(self, cr, uid, ids, context={}):
907 for proc in self.browse(cr, uid, ids, context):
909 if not proc.move_id.state=='done':
914 # This method may be overrided by objects that override mrp.procurment
915 # for computing their own purpose
917 def _quantity_compute_get(self, cr, uid, proc, context={}):
918 if proc.product_id.type=='product':
919 if proc.move_id.product_uos:
920 return proc.move_id.product_uos_qty
923 def _uom_compute_get(self, cr, uid, proc, context={}):
924 if proc.product_id.type=='product':
925 if proc.move_id.product_uos:
926 return proc.move_id.product_uos.id
930 # Return the quantity of product shipped/produced/served, wich may be
931 # different from the planned quantity
933 def quantity_get(self, cr, uid, id, context={}):
934 proc = self.browse(cr, uid, id, context)
935 result = self._quantity_compute_get(cr, uid, proc, context)
937 result = proc.product_qty
940 def uom_get(self, cr, uid, id, context=None):
941 proc = self.browse(cr, uid, id, context)
942 result = self._uom_compute_get(cr, uid, proc, context)
944 result = proc.product_uom.id
947 def check_waiting(self, cr, uid, ids, context=[]):
948 for procurement in self.browse(cr, uid, ids, context=context):
949 if procurement.move_id and procurement.move_id.state=='auto':
953 def check_produce_service(self, cr, uid, procurement, context=[]):
956 def check_produce_product(self, cr, uid, procurement, context=[]):
957 properties = [x.id for x in procurement.property_ids]
958 bom_id = self.pool.get('mrp.bom')._bom_find(cr, uid, procurement.product_id.id, procurement.product_uom.id, properties)
960 cr.execute('update mrp_procurement set message=%s where id=%s', (_('No BoM defined for this product !'), procurement.id))
964 def check_make_to_stock(self, cr, uid, ids, context={}):
966 for procurement in self.browse(cr, uid, ids, context=context):
967 if procurement.product_id.type=='service':
968 ok = ok and self._check_make_to_stock_service(cr, uid, procurement, context)
970 ok = ok and self._check_make_to_stock_product(cr, uid, procurement, context)
973 def check_produce(self, cr, uid, ids, context={}):
975 user = self.pool.get('res.users').browse(cr, uid, uid)
976 for procurement in self.browse(cr, uid, ids):
977 if procurement.product_id.product_tmpl_id.supply_method<>'produce':
978 if procurement.product_id.seller_ids:
979 partner = procurement.product_id.seller_ids[0].name
980 if user.company_id and user.company_id.partner_id:
981 if partner.id == user.company_id.partner_id.id:
984 if procurement.product_id.product_tmpl_id.type=='service':
985 res = res and self.check_produce_service(cr, uid, procurement, context)
987 res = res and self.check_produce_product(cr, uid, procurement, context)
992 def check_buy(self, cr, uid, ids):
993 user = self.pool.get('res.users').browse(cr, uid, uid)
994 for procurement in self.browse(cr, uid, ids):
995 if procurement.product_id.product_tmpl_id.supply_method<>'buy':
997 if not procurement.product_id.seller_ids:
998 cr.execute('update mrp_procurement set message=%s where id=%s', (_('No supplier defined for this product !'), procurement.id))
1000 partner = procurement.product_id.seller_ids[0].name
1001 if user.company_id and user.company_id.partner_id:
1002 if partner.id == user.company_id.partner_id.id:
1004 address_id = self.pool.get('res.partner').address_get(cr, uid, [partner.id], ['delivery'])['delivery']
1006 cr.execute('update mrp_procurement set message=%s where id=%s', (_('No address defined for the supplier'), procurement.id))
1010 def test_cancel(self, cr, uid, ids):
1011 for record in self.browse(cr, uid, ids):
1012 if record.move_id and record.move_id.state=='cancel':
1016 def action_confirm(self, cr, uid, ids, context={}):
1017 for procurement in self.browse(cr, uid, ids):
1018 if procurement.product_qty <= 0.00:
1019 raise osv.except_osv(_('Data Insufficient !'), _('Please check the Quantity of Requisition Order(s), it should not be less than 1!'))
1020 if procurement.product_id.type in ('product', 'consu'):
1021 if not procurement.move_id:
1022 source = procurement.location_id.id
1023 if procurement.procure_method=='make_to_order':
1024 source = procurement.product_id.product_tmpl_id.property_stock_procurement.id
1025 id = self.pool.get('stock.move').create(cr, uid, {
1026 'name': 'PROC:'+procurement.name,
1027 'location_id': source,
1028 'location_dest_id': procurement.location_id.id,
1029 'product_id': procurement.product_id.id,
1030 'product_qty':procurement.product_qty,
1031 'product_uom': procurement.product_uom.id,
1032 'date_planned': procurement.date_planned,
1033 'state':'confirmed',
1034 'company_id': procurement.company_id.id,
1036 self.write(cr, uid, [procurement.id], {'move_id': id, 'close_move':1})
1039 if procurement.procure_method=='make_to_stock' and procurement.move_id.state in ('waiting',):
1040 id = self.pool.get('stock.move').write(cr, uid, [procurement.move_id.id], {'state':'confirmed'})
1041 self.write(cr, uid, ids, {'state':'confirmed','message':''})
1044 def action_move_assigned(self, cr, uid, ids, context={}):
1045 self.write(cr, uid, ids, {'state':'running','message':_('from stock: products assigned.')})
1048 def _check_make_to_stock_service(self, cr, uid, procurement, context={}):
1051 def _check_make_to_stock_product(self, cr, uid, procurement, context={}):
1053 if procurement.move_id:
1054 id = procurement.move_id.id
1055 if not (procurement.move_id.state in ('done','assigned','cancel')):
1056 ok = ok and self.pool.get('stock.move').action_assign(cr, uid, [id])
1057 cr.execute('select count(id) from stock_warehouse_orderpoint where product_id=%s', (procurement.product_id.id,))
1058 if not cr.fetchone()[0]:
1059 cr.execute('update mrp_procurement set message=%s where id=%s', (_('from stock and no minimum orderpoint rule defined'), procurement.id))
1062 def action_produce_assign_service(self, cr, uid, ids, context={}):
1063 for procurement in self.browse(cr, uid, ids):
1064 self.write(cr, uid, [procurement.id], {'state':'running'})
1067 def action_produce_assign_product(self, cr, uid, ids, context={}):
1069 company = self.pool.get('res.users').browse(cr, uid, uid, context).company_id
1070 for procurement in self.browse(cr, uid, ids):
1071 res_id = procurement.move_id.id
1072 loc_id = procurement.location_id.id
1073 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)
1074 newdate = newdate - DateTime.RelativeDateTime(days=company.manufacturing_lead)
1075 produce_id = self.pool.get('mrp.production').create(cr, uid, {
1076 'origin': procurement.origin,
1077 'product_id': procurement.product_id.id,
1078 'product_qty': procurement.product_qty,
1079 'product_uom': procurement.product_uom.id,
1080 'product_uos_qty': procurement.product_uos and procurement.product_uos_qty or False,
1081 'product_uos': procurement.product_uos and procurement.product_uos.id or False,
1082 'location_src_id': procurement.location_id.id,
1083 'location_dest_id': procurement.location_id.id,
1084 'bom_id': procurement.bom_id and procurement.bom_id.id or False,
1085 'date_planned': newdate.strftime('%Y-%m-%d %H:%M:%S'),
1086 'move_prod_id': res_id,
1087 'company_id': procurement.company_id.id,
1089 self.write(cr, uid, [procurement.id], {'state':'running'})
1090 bom_result = self.pool.get('mrp.production').action_compute(cr, uid,
1091 [produce_id], properties=[x.id for x in procurement.property_ids])
1092 wf_service = netsvc.LocalService("workflow")
1093 wf_service.trg_validate(uid, 'mrp.production', produce_id, 'button_confirm', cr)
1094 self.pool.get('stock.move').write(cr, uid, [res_id],
1095 {'location_id':procurement.location_id.id})
1098 def action_po_assign(self, cr, uid, ids, context={}):
1100 company = self.pool.get('res.users').browse(cr, uid, uid, context).company_id
1101 for procurement in self.browse(cr, uid, ids):
1102 res_id = procurement.move_id.id
1103 partner = procurement.product_id.seller_ids[0].name
1104 partner_id = partner.id
1105 address_id = self.pool.get('res.partner').address_get(cr, uid, [partner_id], ['delivery'])['delivery']
1106 pricelist_id = partner.property_product_pricelist_purchase.id
1108 uom_id = procurement.product_id.uom_po_id.id
1110 qty = self.pool.get('product.uom')._compute_qty(cr, uid, procurement.product_uom.id, procurement.product_qty, uom_id)
1111 if procurement.product_id.seller_ids[0].qty:
1112 qty=max(qty,procurement.product_id.seller_ids[0].qty)
1114 price = self.pool.get('product.pricelist').price_get(cr, uid, [pricelist_id], procurement.product_id.id, qty, False, {'uom': uom_id})[pricelist_id]
1116 newdate = DateTime.strptime(procurement.date_planned, '%Y-%m-%d %H:%M:%S')
1117 newdate = newdate - DateTime.RelativeDateTime(days=company.po_lead)
1118 newdate = newdate - procurement.product_id.seller_ids[0].delay
1120 #Passing partner_id to context for purchase order line integrity of Line name
1121 context.update({'lang':partner.lang, 'partner_id':partner_id})
1123 product=self.pool.get('product.product').browse(cr,uid,procurement.product_id.id,context=context)
1126 'name': product.partner_ref,
1128 'product_id': procurement.product_id.id,
1129 'product_uom': uom_id,
1130 'price_unit': price,
1131 'date_planned': newdate.strftime('%Y-%m-%d %H:%M:%S'),
1132 'move_dest_id': res_id,
1133 'notes':product.description_purchase,
1136 taxes_ids = procurement.product_id.product_tmpl_id.supplier_taxes_id
1137 taxes = self.pool.get('account.fiscal.position').map_tax(cr, uid, partner.property_account_position, taxes_ids)
1139 'taxes_id':[(6,0,taxes)]
1141 purchase_id = self.pool.get('purchase.order').create(cr, uid, {
1142 'origin': procurement.origin,
1143 'partner_id': partner_id,
1144 'partner_address_id': address_id,
1145 'location_id': procurement.location_id.id,
1146 'pricelist_id': pricelist_id,
1147 'order_line': [(0,0,line)],
1148 'company_id': procurement.company_id.id,
1149 'fiscal_position': partner.property_account_position and partner.property_account_position.id or False
1151 self.write(cr, uid, [procurement.id], {'state':'running', 'purchase_id':purchase_id})
1154 def action_cancel(self, cr, uid, ids):
1157 for proc in self.browse(cr, uid, ids):
1159 if proc.move_id.state not in ('done','cancel'):
1160 todo2.append(proc.move_id.id)
1162 if proc.move_id and proc.move_id.state=='waiting':
1163 todo.append(proc.move_id.id)
1165 self.pool.get('stock.move').action_cancel(cr, uid, todo2)
1167 self.pool.get('stock.move').write(cr, uid, todo, {'state':'assigned'})
1168 self.write(cr, uid, ids, {'state':'cancel'})
1169 wf_service = netsvc.LocalService("workflow")
1171 wf_service.trg_trigger(uid, 'mrp.procurement', id, cr)
1174 def action_check_finnished(self, cr, uid, ids):
1175 return self.check_move_done(cr, uid, ids)
1177 def action_check(self, cr, uid, ids):
1179 for procurement in self.browse(cr, uid, ids):
1180 if procurement.move_id.state=='assigned' or procurement.move_id.state=='done':
1181 self.action_done(cr, uid, [procurement.id])
1185 def action_ready(self, cr, uid, ids):
1186 res = self.write(cr, uid, ids, {'state':'ready'})
1189 def action_done(self, cr, uid, ids):
1190 for procurement in self.browse(cr, uid, ids):
1191 if procurement.move_id:
1192 if procurement.close_move and (procurement.move_id.state <> 'done'):
1193 self.pool.get('stock.move').action_done(cr, uid, [procurement.move_id.id])
1194 res = self.write(cr, uid, ids, {'state':'done', 'date_close':time.strftime('%Y-%m-%d')})
1195 wf_service = netsvc.LocalService("workflow")
1197 wf_service.trg_trigger(uid, 'mrp.procurement', id, cr)
1200 def run_scheduler(self, cr, uid, automatic=False, use_new_cursor=False, context=None):
1202 use_new_cursor: False or the dbname
1206 self._procure_confirm(cr, uid, use_new_cursor=use_new_cursor, context=context)
1207 self._procure_orderpoint_confirm(cr, uid, automatic=automatic,\
1208 use_new_cursor=use_new_cursor, context=context)
1212 class stock_warehouse_orderpoint(osv.osv):
1213 _name = "stock.warehouse.orderpoint"
1214 _description = "Orderpoint minimum rule"
1216 'name': fields.char('Name', size=32, required=True),
1217 'active': fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the orderpoint without removing it."),
1218 'logic': fields.selection([('max','Order to Max'),('price','Best price (not yet active!)')], 'Reordering Mode', required=True),
1219 'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse', required=True),
1220 'location_id': fields.many2one('stock.location', 'Location', required=True),
1221 'product_id': fields.many2one('product.product', 'Product', required=True, domain=[('type','=','product')]),
1222 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True ),
1223 'product_min_qty': fields.float('Min Quantity', required=True,
1224 help="When the virtual stock goes belong the Min Quantity, Open ERP generates "\
1225 "a requisition to bring the virtual stock to the Max Quantity."),
1226 'product_max_qty': fields.float('Max Quantity', required=True,
1227 help="When the virtual stock goes belong the Min Quantity, Open ERP generates "\
1228 "a requisition to bring the virtual stock to the Max Quantity."),
1229 'qty_multiple': fields.integer('Qty Multiple', required=True,
1230 help="The requisition quantity will by rounded up to this multiple."),
1231 'procurement_id': fields.many2one('mrp.procurement', 'Purchase Order'),
1232 'company_id': fields.many2one('res.company','Company',required=True),
1235 'active': lambda *a: 1,
1236 'logic': lambda *a: 'max',
1237 'qty_multiple': lambda *a: 1,
1238 'name': lambda x,y,z,c: x.pool.get('ir.sequence').get(y,z,'mrp.warehouse.orderpoint') or '',
1239 'product_uom': lambda sel, cr, uid, context: context.get('product_uom', False),
1240 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'stock.warehouse.orderpoint', context=c)
1242 def onchange_warehouse_id(self, cr, uid, ids, warehouse_id, context={}):
1244 w=self.pool.get('stock.warehouse').browse(cr,uid,warehouse_id, context)
1245 v = {'location_id':w.lot_stock_id.id}
1248 def onchange_product_id(self, cr, uid, ids, product_id, context={}):
1250 prod=self.pool.get('product.product').browse(cr,uid,product_id)
1251 v = {'product_uom':prod.uom_id.id}
1254 def copy(self, cr, uid, id, default=None,context={}):
1258 'name': self.pool.get('ir.sequence').get(cr, uid, 'mrp.warehouse.orderpoint') or '',
1260 return super(stock_warehouse_orderpoint, self).copy(cr, uid, id, default, context)
1261 stock_warehouse_orderpoint()
1264 class StockMove(osv.osv):
1265 _inherit = 'stock.move'
1267 'procurements': fields.one2many('mrp.procurement', 'move_id', 'Requisitions'),
1269 def copy(self, cr, uid, id, default=None, context=None):
1270 default = default or {}
1271 default['procurements'] = []
1272 return super(StockMove, self).copy(cr, uid, id, default, context)
1274 def _action_explode(self, cr, uid, move, context={}):
1275 if move.product_id.supply_method=='produce' and move.product_id.procure_method=='make_to_order':
1276 bis = self.pool.get('mrp.bom').search(cr, uid, [
1277 ('product_id','=',move.product_id.id),
1278 ('bom_id','=',False),
1279 ('type','=','phantom')])
1281 factor = move.product_qty
1282 bom_point = self.pool.get('mrp.bom').browse(cr, uid, bis[0])
1283 res = self.pool.get('mrp.bom')._bom_explode(cr, uid, bom_point, factor, [])
1284 dest = move.product_id.product_tmpl_id.property_stock_production.id
1286 if move.state=='assigned':
1291 'picking_id': move.picking_id.id,
1292 'product_id': line['product_id'],
1293 'product_uom': line['product_uom'],
1294 'product_qty': line['product_qty'],
1295 'product_uos': line['product_uos'],
1296 'product_uos_qty': line['product_uos_qty'],
1297 'move_dest_id': move.id,
1299 'name': line['name'],
1300 'location_dest_id': dest,
1301 'move_history_ids': [(6,0,[move.id])],
1302 'move_history_ids2': [(6,0,[])],
1305 mid = self.pool.get('stock.move').copy(cr, uid, move.id, default=valdef)
1306 prodobj = self.pool.get('product.product').browse(cr, uid, line['product_id'], context=context)
1307 proc_id = self.pool.get('mrp.procurement').create(cr, uid, {
1308 'name': (move.picking_id.origin or ''),
1309 'origin': (move.picking_id.origin or ''),
1310 'date_planned': move.date_planned,
1311 'product_id': line['product_id'],
1312 'product_qty': line['product_qty'],
1313 'product_uom': line['product_uom'],
1314 'product_uos_qty': line['product_uos'] and line['product_uos_qty'] or False,
1315 'product_uos': line['product_uos'],
1316 'location_id': move.location_id.id,
1317 'procure_method': prodobj.procure_method,
1319 'company_id': line['company_id'],
1321 wf_service = netsvc.LocalService("workflow")
1322 wf_service.trg_validate(uid, 'mrp.procurement', proc_id, 'button_confirm', cr)
1323 self.pool.get('stock.move').write(cr, uid, [move.id], {
1324 'location_id': move.location_dest_id.id,
1325 'auto_validate': True,
1326 'picking_id': False,
1327 'location_id': dest,
1330 for m in self.pool.get('mrp.procurement').search(cr, uid, [('move_id','=',move.id)], context):
1331 wf_service = netsvc.LocalService("workflow")
1332 wf_service.trg_validate(uid, 'mrp.procurement', m, 'button_wait_done', cr)
1336 def consume_moves(self, cr, uid, ids, product_qty, location_id, context=None):
1338 production_obj = self.pool.get('mrp.production')
1339 for move in self.browse(cr, uid, ids):
1340 new_moves = super(StockMove, self).consume_moves(cr, uid, ids, product_qty, location_id, context=context)
1341 production_ids = production_obj.search(cr, uid, [('move_lines', 'in', [move.id])])
1342 for new_move in new_moves:
1343 production_obj.write(cr, uid, production_ids, {'move_lines': [(4, new_move)]})
1344 res.append(new_move)
1347 def split_lines(self, cr, uid, ids, quantity, split_by_qty=1, prefix=False, context=None):
1349 production_obj = self.pool.get('mrp.production')
1350 for move in self.browse(cr, uid, ids):
1351 new_moves = super(StockMove, self).split_lines(cr, uid, [move.id], quantity, split_by_qty, prefix, context=context)
1352 production_ids = production_obj.search(cr, uid, [('move_lines', 'in', [move.id])])
1353 for new_move in new_moves:
1354 production_obj.write(cr, uid, production_ids, {'move_lines': [(4, new_move)]})
1355 res.append(new_move)
1358 def scrap_moves(self, cr, uid, ids, quantity, location_dest_id, context=None):
1360 production_obj = self.pool.get('mrp.production')
1361 for move in self.browse(cr, uid, ids):
1362 new_moves = super(StockMove, self).scrap_moves(cr, uid, [move.id], quantity, location_dest_id, context=context)
1363 production_ids = production_obj.search(cr, uid, [('move_lines', 'in', [move.id])])
1364 for new_move in new_moves:
1365 production_obj.write(cr, uid, production_ids, {'move_lines': [(4, new_move)]})
1366 res.append(new_move)
1372 class StockPicking(osv.osv):
1373 _inherit = 'stock.picking'
1375 def test_finnished(self, cursor, user, ids):
1376 wf_service = netsvc.LocalService("workflow")
1377 res = super(StockPicking, self).test_finnished(cursor, user, ids)
1378 for picking in self.browse(cursor, user, ids):
1379 for move in picking.move_lines:
1380 if move.state == 'done' and move.procurements:
1381 for procurement in move.procurements:
1382 wf_service.trg_validate(user, 'mrp.procurement',
1383 procurement.id, 'button_check', cursor)
1387 # Explode picking by replacing phantom BoMs
1389 def action_explode(self, cr, uid, picks, *args):
1390 for move in self.pool.get('stock.move').browse(cr, uid, picks):
1391 self.pool.get('stock.move')._action_explode(cr, uid, move)
1397 class spilt_in_production_lot(osv.osv_memory):
1398 _inherit = "stock.move.spilt"
1399 def split(self, cr, uid, ids, move_ids, context=None):
1400 production_obj = self.pool.get('mrp.production')
1401 move_obj = self.pool.get('stock.move')
1403 for move in move_obj.browse(cr, uid, move_ids, context=context):
1404 new_moves = super(spilt_in_production_lot, self).split(cr, uid, ids, move_ids, context=context)
1405 production_ids = production_obj.search(cr, uid, [('move_lines', 'in', [move.id])])
1406 for new_move in new_moves:
1407 production_obj.write(cr, uid, production_ids, {'move_lines': [(4, new_move)]})
1409 spilt_in_production_lot()
1411 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: