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 def action_start(self, cr, uid, ids, context=None):
585 self.write(cr, uid, ids, {'state':'in_production'})
588 #TODO Review materials in function in_prod and prod_end.
589 def action_production_end(self, cr, uid, ids):
591 for production in self.browse(cr, uid, ids):
592 for res in production.move_lines:
593 for move in production.move_created_ids:
594 #XXX must use the orm
595 cr.execute('INSERT INTO stock_move_history_ids \
596 (parent_id, child_id) VALUES (%s,%s)',
598 # move_ids.append(res.id)
599 vals= {'state':'confirmed'}
600 new_moves = [x.id for x in production.move_created_ids]
601 self.pool.get('stock.move').write(cr, uid, new_moves, vals)
602 if not production.date_finnished:
603 self.write(cr, uid, [production.id],
604 {'date_finnished': time.strftime('%Y-%m-%d %H:%M:%S')})
605 self.pool.get('stock.move').check_assign(cr, uid, new_moves)
606 self.pool.get('stock.move').action_done(cr, uid, new_moves)
607 self._costs_generate(cr, uid, production)
608 self.pool.get('stock.move').action_done(cr, uid, move_ids)
609 self.write(cr, uid, ids, {'state': 'done'})
612 def _costs_generate(self, cr, uid, production):
614 for wc_line in production.workcenter_lines:
615 wc = wc_line.workcenter_id
616 if wc.costs_journal_id and wc.costs_general_account_id:
617 value = wc_line.hour * wc.costs_hour
618 account = wc.costs_hour_account_id.id
619 if value and account:
621 self.pool.get('account.analytic.line').create(cr, uid, {
622 'name': wc_line.name+' (H)',
624 'account_id': account,
625 'general_account_id': wc.costs_general_account_id.id,
626 'journal_id': wc.costs_journal_id.id,
629 if wc.costs_journal_id and wc.costs_general_account_id:
630 value = wc_line.cycle * wc.costs_cycle
631 account = wc.costs_cycle_account_id.id
632 if value and account:
634 self.pool.get('account.analytic.line').create(cr, uid, {
635 'name': wc_line.name+' (C)',
637 'account_id': account,
638 'general_account_id': wc.costs_general_account_id.id,
639 'journal_id': wc.costs_journal_id.id,
644 def action_in_production(self, cr, uid, ids):
646 for production in self.browse(cr, uid, ids):
647 for res in production.move_lines:
648 move_ids.append(res.id)
649 if not production.date_start:
650 self.write(cr, uid, [production.id],
651 {'date_start': time.strftime('%Y-%m-%d %H:%M:%S')})
652 # self.pool.get('stock.move').action_done(cr, uid, move_ids)
653 self.write(cr, uid, ids, {'state': 'in_production'})
656 def test_if_product(self, cr, uid, ids):
658 for production in self.browse(cr, uid, ids):
659 if not production.product_lines:
660 if not self.action_compute(cr, uid, [production.id]):
664 def _get_auto_picking(self, cr, uid, production):
667 def action_confirm(self, cr, uid, ids):
670 for production in self.browse(cr, uid, ids):
671 if not production.product_lines:
672 self.action_compute(cr, uid, [production.id])
673 production = self.browse(cr, uid, [production.id])[0]
675 pick_type = 'internal'
677 if production.bom_id.routing_id and production.bom_id.routing_id.location_id:
678 routing_loc = production.bom_id.routing_id.location_id
679 if routing_loc.usage<>'internal':
681 address_id = routing_loc.address_id and routing_loc.address_id.id or False
682 routing_loc = routing_loc.id
683 picking_id = self.pool.get('stock.picking').create(cr, uid, {
684 'origin': (production.origin or '').split(':')[0] +':'+production.name,
688 'address_id': address_id,
689 'auto_picking': self._get_auto_picking(cr, uid, production),
690 'company_id': production.company_id.id,
693 source = production.product_id.product_tmpl_id.property_stock_production.id
695 'name':'PROD:'+production.name,
696 'date_planned': production.date_planned,
697 'product_id': production.product_id.id,
698 'product_qty': production.product_qty,
699 'product_uom': production.product_uom.id,
700 'product_uos_qty': production.product_uos and production.product_uos_qty or False,
701 'product_uos': production.product_uos and production.product_uos.id or False,
702 'location_id': source,
703 'location_dest_id': production.location_dest_id.id,
704 'move_dest_id': production.move_prod_id.id,
706 'company_id': production.company_id.id,
708 res_final_id = self.pool.get('stock.move').create(cr, uid, data)
710 self.write(cr, uid, [production.id], {'move_created_ids': [(6, 0, [res_final_id])]})
712 for line in production.product_lines:
714 newdate = production.date_planned
715 if line.product_id.type in ('product', 'consu'):
716 res_dest_id = self.pool.get('stock.move').create(cr, uid, {
717 'name':'PROD:'+production.name,
718 'date_planned': production.date_planned,
719 'product_id': line.product_id.id,
720 'product_qty': line.product_qty,
721 'product_uom': line.product_uom.id,
722 'product_uos_qty': line.product_uos and line.product_uos_qty or False,
723 'product_uos': line.product_uos and line.product_uos.id or False,
724 'location_id': routing_loc or production.location_src_id.id,
725 'location_dest_id': source,
726 'move_dest_id': res_final_id,
728 'company_id': production.company_id.id,
730 moves.append(res_dest_id)
731 move_id = self.pool.get('stock.move').create(cr, uid, {
732 'name':'PROD:'+production.name,
733 'picking_id':picking_id,
734 'product_id': line.product_id.id,
735 'product_qty': line.product_qty,
736 'product_uom': line.product_uom.id,
737 'product_uos_qty': line.product_uos and line.product_uos_qty or False,
738 'product_uos': line.product_uos and line.product_uos.id or False,
739 'date_planned': newdate,
740 'move_dest_id': res_dest_id,
741 'location_id': production.location_src_id.id,
742 'location_dest_id': routing_loc or production.location_src_id.id,
744 'company_id': production.company_id.id,
746 proc_id = self.pool.get('mrp.procurement').create(cr, uid, {
747 'name': (production.origin or '').split(':')[0] + ':' + production.name,
748 'origin': (production.origin or '').split(':')[0] + ':' + production.name,
749 'date_planned': newdate,
750 'product_id': line.product_id.id,
751 'product_qty': line.product_qty,
752 'product_uom': line.product_uom.id,
753 'product_uos_qty': line.product_uos and line.product_qty or False,
754 'product_uos': line.product_uos and line.product_uos.id or False,
755 'location_id': production.location_src_id.id,
756 'procure_method': line.product_id.procure_method,
758 'company_id': production.company_id.id,
760 wf_service = netsvc.LocalService("workflow")
761 wf_service.trg_validate(uid, 'mrp.procurement', proc_id, 'button_confirm', cr)
762 proc_ids.append(proc_id)
763 wf_service = netsvc.LocalService("workflow")
764 wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
765 self.write(cr, uid, [production.id], {'picking_id':picking_id, 'move_lines': [(6,0,moves)], 'state':'confirmed'})
768 def force_production(self, cr, uid, ids, *args):
769 pick_obj = self.pool.get('stock.picking')
770 pick_obj.force_assign(cr, uid, [prod.picking_id.id for prod in self.browse(cr, uid, ids)])
776 class stock_move(osv.osv):
778 _inherit = 'stock.move'
780 'production_id': fields.many2one('mrp.production', 'Production', select=True),
784 class mrp_production_workcenter_line(osv.osv):
785 _name = 'mrp.production.workcenter.line'
786 _description = 'Work Orders'
789 'name': fields.char('Work Order', size=64, required=True),
790 'workcenter_id': fields.many2one('mrp.workcenter', 'Work Center', required=True),
791 'cycle': fields.float('Nbr of cycles', digits=(16,2)),
792 'hour': fields.float('Nbr of hours', digits=(16,2)),
793 'sequence': fields.integer('Sequence', required=True, help="Gives the sequence order when displaying a list of work orders."),
794 'production_id': fields.many2one('mrp.production', 'Production Order', select=True, ondelete='cascade'),
797 'sequence': lambda *a: 1,
798 'hour': lambda *a: 0,
799 'cycle': lambda *a: 0,
801 mrp_production_workcenter_line()
803 class mrp_production_product_line(osv.osv):
804 _name = 'mrp.production.product.line'
805 _description = 'Production scheduled products'
807 'name': fields.char('Name', size=64, required=True),
808 'product_id': fields.many2one('product.product', 'Product', required=True),
809 'product_qty': fields.float('Product Qty', required=True),
810 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True),
811 'product_uos_qty': fields.float('Product UOS Qty'),
812 'product_uos': fields.many2one('product.uom', 'Product UOS'),
813 'production_id': fields.many2one('mrp.production', 'Production Order', select=True),
815 mrp_production_product_line()
817 # ------------------------------------------------------------------
819 # ------------------------------------------------------------------
821 # Produce, Buy or Find products and place a move
822 # then wizard for picking lists & move
824 class mrp_procurement(osv.osv):
825 _name = "mrp.procurement"
826 _description = "Procurement"
827 _order = 'priority,date_planned'
829 'name': fields.char('Reason', size=64, required=True, help='Requisition name.'),
830 'origin': fields.char('Source Document', size=64,
831 help="Reference of the document that created this Requisition.\n"
832 "This is automatically completed by Open ERP."),
833 'priority': fields.selection([('0','Not urgent'),('1','Normal'),('2','Urgent'),('3','Very Urgent')], 'Priority', required=True),
834 'date_planned': fields.datetime('Scheduled date', required=True),
835 'date_close': fields.datetime('Date Closed'),
836 'product_id': fields.many2one('product.product', 'Product', required=True, states={'draft':[('readonly',False)]}, readonly=True),
837 'product_qty': fields.float('Quantity', required=True, states={'draft':[('readonly',False)]}, readonly=True),
838 'product_uom': fields.many2one('product.uom', 'Product UoM', required=True, states={'draft':[('readonly',False)]}, readonly=True),
839 'product_uos_qty': fields.float('UoS Quantity', states={'draft':[('readonly',False)]}, readonly=True),
840 'product_uos': fields.many2one('product.uom', 'Product UoS', states={'draft':[('readonly',False)]}, readonly=True),
841 'move_id': fields.many2one('stock.move', 'Reservation', ondelete='set null'),
843 'bom_id': fields.many2one('mrp.bom', 'BoM', ondelete='cascade', select=True),
845 'close_move': fields.boolean('Close Move at end', required=True),
846 'location_id': fields.many2one('stock.location', 'Location', required=True, states={'draft':[('readonly',False)]}, readonly=True),
847 'procure_method': fields.selection([('make_to_stock','from stock'),('make_to_order','on order')], 'Procurement Method', states={'draft':[('readonly',False)], 'confirmed':[('readonly',False)]},
848 readonly=True, required=True, help="If you encode manually a Procurement, you probably want to use" \
849 " a make to order method."),
851 'purchase_id': fields.many2one('purchase.order', 'Purchase Order'),
852 'note': fields.text('Note'),
854 'property_ids': fields.many2many('mrp.property', 'mrp_procurement_property_rel', 'procurement_id','property_id', 'Properties'),
856 'message': fields.char('Latest error', size=64, help="Exception occurred while computing procurement orders."),
857 'state': fields.selection([
859 ('confirmed','Confirmed'),
860 ('exception','Exception'),
861 ('running','Running'),
865 ('waiting','Waiting')], 'State', required=True,
866 help='When a procurement is created the state is set to \'Draft\'.\n If the procurement is confirmed, the state is set to \'Confirmed\'.\
867 \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.'),
868 'note' : fields.text('Note'),
869 'company_id': fields.many2one('res.company','Company',required=True),
872 'state': lambda *a: 'draft',
873 'priority': lambda *a: '1',
874 'date_planned': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
875 'close_move': lambda *a: 0,
876 'procure_method': lambda *a: 'make_to_order',
877 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'mrp.procurement', context=c)
880 def unlink(self, cr, uid, ids, context=None):
881 procurements = self.read(cr, uid, ids, ['state'])
883 for s in procurements:
884 if s['state'] in ['draft','cancel']:
885 unlink_ids.append(s['id'])
887 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Requisition Order(s) which are in %s State!' % s['state']))
888 return osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
890 def onchange_product_id(self, cr, uid, ids, product_id, context={}):
892 w=self.pool.get('product.product').browse(cr,uid,product_id, context)
894 'product_uom':w.uom_id.id,
895 'product_uos':w.uos_id and w.uos_id.id or w.uom_id.id
900 def check_product(self, cr, uid, ids):
901 for procurement in self.browse(cr, uid, ids):
902 if procurement.product_id.type in ('product', 'consu'):
906 def check_move_cancel(self, cr, uid, ids, context={}):
909 for procurement in self.browse(cr, uid, ids, context):
910 if procurement.move_id:
912 if not procurement.move_id.state=='cancel':
916 def check_move_done(self, cr, uid, ids, context={}):
918 for proc in self.browse(cr, uid, ids, context):
920 if not proc.move_id.state=='done':
925 # This method may be overrided by objects that override mrp.procurment
926 # for computing their own purpose
928 def _quantity_compute_get(self, cr, uid, proc, context={}):
929 if proc.product_id.type=='product':
930 if proc.move_id.product_uos:
931 return proc.move_id.product_uos_qty
934 def _uom_compute_get(self, cr, uid, proc, context={}):
935 if proc.product_id.type=='product':
936 if proc.move_id.product_uos:
937 return proc.move_id.product_uos.id
941 # Return the quantity of product shipped/produced/served, wich may be
942 # different from the planned quantity
944 def quantity_get(self, cr, uid, id, context={}):
945 proc = self.browse(cr, uid, id, context)
946 result = self._quantity_compute_get(cr, uid, proc, context)
948 result = proc.product_qty
951 def uom_get(self, cr, uid, id, context=None):
952 proc = self.browse(cr, uid, id, context)
953 result = self._uom_compute_get(cr, uid, proc, context)
955 result = proc.product_uom.id
958 def check_waiting(self, cr, uid, ids, context=[]):
959 for procurement in self.browse(cr, uid, ids, context=context):
960 if procurement.move_id and procurement.move_id.state=='auto':
964 def check_produce_service(self, cr, uid, procurement, context=[]):
967 def check_produce_product(self, cr, uid, procurement, context=[]):
968 properties = [x.id for x in procurement.property_ids]
969 bom_id = self.pool.get('mrp.bom')._bom_find(cr, uid, procurement.product_id.id, procurement.product_uom.id, properties)
971 cr.execute('update mrp_procurement set message=%s where id=%s', (_('No BoM defined for this product !'), procurement.id))
975 def check_make_to_stock(self, cr, uid, ids, context={}):
977 for procurement in self.browse(cr, uid, ids, context=context):
978 if procurement.product_id.type=='service':
979 ok = ok and self._check_make_to_stock_service(cr, uid, procurement, context)
981 ok = ok and self._check_make_to_stock_product(cr, uid, procurement, context)
984 def check_produce(self, cr, uid, ids, context={}):
986 user = self.pool.get('res.users').browse(cr, uid, uid)
987 for procurement in self.browse(cr, uid, ids):
988 if procurement.product_id.product_tmpl_id.supply_method<>'produce':
989 if procurement.product_id.seller_ids:
990 partner = procurement.product_id.seller_ids[0].name
991 if user.company_id and user.company_id.partner_id:
992 if partner.id == user.company_id.partner_id.id:
995 if procurement.product_id.product_tmpl_id.type=='service':
996 res = res and self.check_produce_service(cr, uid, procurement, context)
998 res = res and self.check_produce_product(cr, uid, procurement, context)
1003 def check_buy(self, cr, uid, ids):
1004 user = self.pool.get('res.users').browse(cr, uid, uid)
1005 for procurement in self.browse(cr, uid, ids):
1006 if procurement.product_id.product_tmpl_id.supply_method<>'buy':
1008 if not procurement.product_id.seller_ids:
1009 cr.execute('update mrp_procurement set message=%s where id=%s', (_('No supplier defined for this product !'), procurement.id))
1011 partner = procurement.product_id.seller_ids[0].name
1012 if user.company_id and user.company_id.partner_id:
1013 if partner.id == user.company_id.partner_id.id:
1015 address_id = self.pool.get('res.partner').address_get(cr, uid, [partner.id], ['delivery'])['delivery']
1017 cr.execute('update mrp_procurement set message=%s where id=%s', (_('No address defined for the supplier'), procurement.id))
1021 def test_cancel(self, cr, uid, ids):
1022 for record in self.browse(cr, uid, ids):
1023 if record.move_id and record.move_id.state=='cancel':
1027 def action_confirm(self, cr, uid, ids, context={}):
1028 for procurement in self.browse(cr, uid, ids):
1029 if procurement.product_qty <= 0.00:
1030 raise osv.except_osv(_('Data Insufficient !'), _('Please check the Quantity of Requisition Order(s), it should not be less than 1!'))
1031 if procurement.product_id.type in ('product', 'consu'):
1032 if not procurement.move_id:
1033 source = procurement.location_id.id
1034 if procurement.procure_method=='make_to_order':
1035 source = procurement.product_id.product_tmpl_id.property_stock_procurement.id
1036 id = self.pool.get('stock.move').create(cr, uid, {
1037 'name': 'PROC:'+procurement.name,
1038 'location_id': source,
1039 'location_dest_id': procurement.location_id.id,
1040 'product_id': procurement.product_id.id,
1041 'product_qty':procurement.product_qty,
1042 'product_uom': procurement.product_uom.id,
1043 'date_planned': procurement.date_planned,
1044 'state':'confirmed',
1045 'company_id': procurement.company_id.id,
1047 self.write(cr, uid, [procurement.id], {'move_id': id, 'close_move':1})
1050 if procurement.procure_method=='make_to_stock' and procurement.move_id.state in ('waiting',):
1051 id = self.pool.get('stock.move').write(cr, uid, [procurement.move_id.id], {'state':'confirmed'})
1052 self.write(cr, uid, ids, {'state':'confirmed','message':''})
1055 def action_move_assigned(self, cr, uid, ids, context={}):
1056 self.write(cr, uid, ids, {'state':'running','message':_('from stock: products assigned.')})
1059 def _check_make_to_stock_service(self, cr, uid, procurement, context={}):
1062 def _check_make_to_stock_product(self, cr, uid, procurement, context={}):
1064 if procurement.move_id:
1065 id = procurement.move_id.id
1066 if not (procurement.move_id.state in ('done','assigned','cancel')):
1067 ok = ok and self.pool.get('stock.move').action_assign(cr, uid, [id])
1068 cr.execute('select count(id) from stock_warehouse_orderpoint where product_id=%s', (procurement.product_id.id,))
1069 if not cr.fetchone()[0]:
1070 cr.execute('update mrp_procurement set message=%s where id=%s', (_('from stock and no minimum orderpoint rule defined'), procurement.id))
1073 def action_produce_assign_service(self, cr, uid, ids, context={}):
1074 for procurement in self.browse(cr, uid, ids):
1075 self.write(cr, uid, [procurement.id], {'state':'running'})
1078 def action_produce_assign_product(self, cr, uid, ids, context={}):
1080 company = self.pool.get('res.users').browse(cr, uid, uid, context).company_id
1081 for procurement in self.browse(cr, uid, ids):
1082 res_id = procurement.move_id.id
1083 loc_id = procurement.location_id.id
1084 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)
1085 newdate = newdate - DateTime.RelativeDateTime(days=company.manufacturing_lead)
1086 produce_id = self.pool.get('mrp.production').create(cr, uid, {
1087 'origin': procurement.origin,
1088 'product_id': procurement.product_id.id,
1089 'product_qty': procurement.product_qty,
1090 'product_uom': procurement.product_uom.id,
1091 'product_uos_qty': procurement.product_uos and procurement.product_uos_qty or False,
1092 'product_uos': procurement.product_uos and procurement.product_uos.id or False,
1093 'location_src_id': procurement.location_id.id,
1094 'location_dest_id': procurement.location_id.id,
1095 'bom_id': procurement.bom_id and procurement.bom_id.id or False,
1096 'date_planned': newdate.strftime('%Y-%m-%d %H:%M:%S'),
1097 'move_prod_id': res_id,
1098 'company_id': procurement.company_id.id,
1100 self.write(cr, uid, [procurement.id], {'state':'running'})
1101 bom_result = self.pool.get('mrp.production').action_compute(cr, uid,
1102 [produce_id], properties=[x.id for x in procurement.property_ids])
1103 wf_service = netsvc.LocalService("workflow")
1104 wf_service.trg_validate(uid, 'mrp.production', produce_id, 'button_confirm', cr)
1105 self.pool.get('stock.move').write(cr, uid, [res_id],
1106 {'location_id':procurement.location_id.id})
1109 def action_po_assign(self, cr, uid, ids, context={}):
1111 company = self.pool.get('res.users').browse(cr, uid, uid, context).company_id
1112 for procurement in self.browse(cr, uid, ids):
1113 res_id = procurement.move_id.id
1114 partner = procurement.product_id.seller_ids[0].name
1115 partner_id = partner.id
1116 address_id = self.pool.get('res.partner').address_get(cr, uid, [partner_id], ['delivery'])['delivery']
1117 pricelist_id = partner.property_product_pricelist_purchase.id
1119 uom_id = procurement.product_id.uom_po_id.id
1121 qty = self.pool.get('product.uom')._compute_qty(cr, uid, procurement.product_uom.id, procurement.product_qty, uom_id)
1122 if procurement.product_id.seller_ids[0].qty:
1123 qty=max(qty,procurement.product_id.seller_ids[0].qty)
1125 price = self.pool.get('product.pricelist').price_get(cr, uid, [pricelist_id], procurement.product_id.id, qty, False, {'uom': uom_id})[pricelist_id]
1127 newdate = DateTime.strptime(procurement.date_planned, '%Y-%m-%d %H:%M:%S')
1128 newdate = newdate - DateTime.RelativeDateTime(days=company.po_lead)
1129 newdate = newdate - procurement.product_id.seller_ids[0].delay
1131 #Passing partner_id to context for purchase order line integrity of Line name
1132 context.update({'lang':partner.lang, 'partner_id':partner_id})
1134 product=self.pool.get('product.product').browse(cr,uid,procurement.product_id.id,context=context)
1137 'name': product.partner_ref,
1139 'product_id': procurement.product_id.id,
1140 'product_uom': uom_id,
1141 'price_unit': price,
1142 'date_planned': newdate.strftime('%Y-%m-%d %H:%M:%S'),
1143 'move_dest_id': res_id,
1144 'notes':product.description_purchase,
1147 taxes_ids = procurement.product_id.product_tmpl_id.supplier_taxes_id
1148 taxes = self.pool.get('account.fiscal.position').map_tax(cr, uid, partner.property_account_position, taxes_ids)
1150 'taxes_id':[(6,0,taxes)]
1152 purchase_id = self.pool.get('purchase.order').create(cr, uid, {
1153 'origin': procurement.origin,
1154 'partner_id': partner_id,
1155 'partner_address_id': address_id,
1156 'location_id': procurement.location_id.id,
1157 'pricelist_id': pricelist_id,
1158 'order_line': [(0,0,line)],
1159 'company_id': procurement.company_id.id,
1160 'fiscal_position': partner.property_account_position and partner.property_account_position.id or False
1162 self.write(cr, uid, [procurement.id], {'state':'running', 'purchase_id':purchase_id})
1165 def action_cancel(self, cr, uid, ids):
1168 for proc in self.browse(cr, uid, ids):
1170 if proc.move_id.state not in ('done','cancel'):
1171 todo2.append(proc.move_id.id)
1173 if proc.move_id and proc.move_id.state=='waiting':
1174 todo.append(proc.move_id.id)
1176 self.pool.get('stock.move').action_cancel(cr, uid, todo2)
1178 self.pool.get('stock.move').write(cr, uid, todo, {'state':'assigned'})
1179 self.write(cr, uid, ids, {'state':'cancel'})
1180 wf_service = netsvc.LocalService("workflow")
1182 wf_service.trg_trigger(uid, 'mrp.procurement', id, cr)
1185 def action_check_finnished(self, cr, uid, ids):
1186 return self.check_move_done(cr, uid, ids)
1188 def action_check(self, cr, uid, ids):
1190 for procurement in self.browse(cr, uid, ids):
1191 if procurement.move_id.state=='assigned' or procurement.move_id.state=='done':
1192 self.action_done(cr, uid, [procurement.id])
1196 def action_ready(self, cr, uid, ids):
1197 res = self.write(cr, uid, ids, {'state':'ready'})
1200 def action_done(self, cr, uid, ids):
1201 for procurement in self.browse(cr, uid, ids):
1202 if procurement.move_id:
1203 if procurement.close_move and (procurement.move_id.state <> 'done'):
1204 self.pool.get('stock.move').action_done(cr, uid, [procurement.move_id.id])
1205 res = self.write(cr, uid, ids, {'state':'done', 'date_close':time.strftime('%Y-%m-%d')})
1206 wf_service = netsvc.LocalService("workflow")
1208 wf_service.trg_trigger(uid, 'mrp.procurement', id, cr)
1211 def run_scheduler(self, cr, uid, automatic=False, use_new_cursor=False, context=None):
1213 use_new_cursor: False or the dbname
1217 self._procure_confirm(cr, uid, use_new_cursor=use_new_cursor, context=context)
1218 self._procure_orderpoint_confirm(cr, uid, automatic=automatic,\
1219 use_new_cursor=use_new_cursor, context=context)
1223 class stock_warehouse_orderpoint(osv.osv):
1224 _name = "stock.warehouse.orderpoint"
1225 _description = "Orderpoint minimum rule"
1227 'name': fields.char('Name', size=32, required=True),
1228 'active': fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the orderpoint without removing it."),
1229 'logic': fields.selection([('max','Order to Max'),('price','Best price (not yet active!)')], 'Reordering Mode', required=True),
1230 'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse', required=True),
1231 'location_id': fields.many2one('stock.location', 'Location', required=True),
1232 'product_id': fields.many2one('product.product', 'Product', required=True, domain=[('type','=','product')]),
1233 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True ),
1234 'product_min_qty': fields.float('Min Quantity', required=True,
1235 help="When the virtual stock goes belong the Min Quantity, Open ERP generates "\
1236 "a requisition to bring the virtual stock to the Max Quantity."),
1237 'product_max_qty': fields.float('Max Quantity', required=True,
1238 help="When the virtual stock goes belong the Min Quantity, Open ERP generates "\
1239 "a requisition to bring the virtual stock to the Max Quantity."),
1240 'qty_multiple': fields.integer('Qty Multiple', required=True,
1241 help="The requisition quantity will by rounded up to this multiple."),
1242 'procurement_id': fields.many2one('mrp.procurement', 'Purchase Order'),
1243 'company_id': fields.many2one('res.company','Company',required=True),
1246 'active': lambda *a: 1,
1247 'logic': lambda *a: 'max',
1248 'qty_multiple': lambda *a: 1,
1249 'name': lambda x,y,z,c: x.pool.get('ir.sequence').get(y,z,'mrp.warehouse.orderpoint') or '',
1250 'product_uom': lambda sel, cr, uid, context: context.get('product_uom', False),
1251 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'stock.warehouse.orderpoint', context=c)
1253 def onchange_warehouse_id(self, cr, uid, ids, warehouse_id, context={}):
1255 w=self.pool.get('stock.warehouse').browse(cr,uid,warehouse_id, context)
1256 v = {'location_id':w.lot_stock_id.id}
1259 def onchange_product_id(self, cr, uid, ids, product_id, context={}):
1261 prod=self.pool.get('product.product').browse(cr,uid,product_id)
1262 v = {'product_uom':prod.uom_id.id}
1265 def copy(self, cr, uid, id, default=None,context={}):
1269 'name': self.pool.get('ir.sequence').get(cr, uid, 'mrp.warehouse.orderpoint') or '',
1271 return super(stock_warehouse_orderpoint, self).copy(cr, uid, id, default, context)
1272 stock_warehouse_orderpoint()
1275 class StockMove(osv.osv):
1276 _inherit = 'stock.move'
1278 'procurements': fields.one2many('mrp.procurement', 'move_id', 'Requisitions'),
1280 def copy(self, cr, uid, id, default=None, context=None):
1281 default = default or {}
1282 default['procurements'] = []
1283 return super(StockMove, self).copy(cr, uid, id, default, context)
1285 def _action_explode(self, cr, uid, move, context={}):
1286 if move.product_id.supply_method=='produce' and move.product_id.procure_method=='make_to_order':
1287 bis = self.pool.get('mrp.bom').search(cr, uid, [
1288 ('product_id','=',move.product_id.id),
1289 ('bom_id','=',False),
1290 ('type','=','phantom')])
1292 factor = move.product_qty
1293 bom_point = self.pool.get('mrp.bom').browse(cr, uid, bis[0])
1294 res = self.pool.get('mrp.bom')._bom_explode(cr, uid, bom_point, factor, [])
1295 dest = move.product_id.product_tmpl_id.property_stock_production.id
1297 if move.state=='assigned':
1302 'picking_id': move.picking_id.id,
1303 'product_id': line['product_id'],
1304 'product_uom': line['product_uom'],
1305 'product_qty': line['product_qty'],
1306 'product_uos': line['product_uos'],
1307 'product_uos_qty': line['product_uos_qty'],
1308 'move_dest_id': move.id,
1310 'name': line['name'],
1311 'location_dest_id': dest,
1312 'move_history_ids': [(6,0,[move.id])],
1313 'move_history_ids2': [(6,0,[])],
1316 mid = self.pool.get('stock.move').copy(cr, uid, move.id, default=valdef)
1317 prodobj = self.pool.get('product.product').browse(cr, uid, line['product_id'], context=context)
1318 proc_id = self.pool.get('mrp.procurement').create(cr, uid, {
1319 'name': (move.picking_id.origin or ''),
1320 'origin': (move.picking_id.origin or ''),
1321 'date_planned': move.date_planned,
1322 'product_id': line['product_id'],
1323 'product_qty': line['product_qty'],
1324 'product_uom': line['product_uom'],
1325 'product_uos_qty': line['product_uos'] and line['product_uos_qty'] or False,
1326 'product_uos': line['product_uos'],
1327 'location_id': move.location_id.id,
1328 'procure_method': prodobj.procure_method,
1330 'company_id': line['company_id'],
1332 wf_service = netsvc.LocalService("workflow")
1333 wf_service.trg_validate(uid, 'mrp.procurement', proc_id, 'button_confirm', cr)
1334 self.pool.get('stock.move').write(cr, uid, [move.id], {
1335 'location_id': move.location_dest_id.id,
1336 'auto_validate': True,
1337 'picking_id': False,
1338 'location_id': dest,
1341 for m in self.pool.get('mrp.procurement').search(cr, uid, [('move_id','=',move.id)], context):
1342 wf_service = netsvc.LocalService("workflow")
1343 wf_service.trg_validate(uid, 'mrp.procurement', m, 'button_wait_done', cr)
1346 def _track_lines(self, cr, uid, ids, data, context=None):
1348 new_move = super(StockMove, self)._track_lines(cr, uid, ids, data, context=context)
1350 production_obj = self.pool.get('mrp.production')
1351 move_obj = self.pool.get('stock.move')
1352 move = move_obj.browse(cr, uid, ids)
1353 production_ids = production_obj.search(cr, uid, [('move_lines', 'in', [move.id])])
1354 for new in new_move:
1355 production_obj.write(cr, uid, production_ids, {'move_lines': [(4, new)]})
1356 move_obj.action_done(cr, uid, [new])
1359 def consume_moves(self, cr, uid, ids, product_qty, location_id, context=None):
1360 new_move = super(StockMove, self).consume_moves(cr, uid, ids, product_qty, location_id, context=context)
1362 production_obj = self.pool.get('mrp.production')
1363 move_obj = self.pool.get('stock.move')
1364 move = move_obj.browse(cr, uid, ids)
1365 production_ids = production_obj.search(cr, uid, [('move_lines', 'in', [move.id])])
1366 production_obj.write(cr, uid, production_ids, {'move_lines': [(4, new_move)]})
1369 def scrap_moves(self, cr, uid, ids, product_qty, location_id, context=None):
1370 new_move = super(StockMove, self).scrap_moves(cr, uid, ids, product_qty, location_id, context=context)
1372 production_obj = self.pool.get('mrp.production')
1373 move_obj = self.pool.get('stock.move')
1374 move = move_obj.browse(cr, uid, ids)
1375 production_ids = production_obj.search(cr, uid, [('move_lines', 'in', [move.id])])
1376 production_obj.write(cr, uid, production_ids, {'move_lines': [(4, new_move)]})
1382 class StockPicking(osv.osv):
1383 _inherit = 'stock.picking'
1385 def test_finnished(self, cursor, user, ids):
1386 wf_service = netsvc.LocalService("workflow")
1387 res = super(StockPicking, self).test_finnished(cursor, user, ids)
1388 for picking in self.browse(cursor, user, ids):
1389 for move in picking.move_lines:
1390 if move.state == 'done' and move.procurements:
1391 for procurement in move.procurements:
1392 wf_service.trg_validate(user, 'mrp.procurement',
1393 procurement.id, 'button_check', cursor)
1397 # Explode picking by replacing phantom BoMs
1399 def action_explode(self, cr, uid, picks, *args):
1400 for move in self.pool.get('stock.move').browse(cr, uid, picks):
1401 self.pool.get('stock.move')._action_explode(cr, uid, move)
1406 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: