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),
192 'multi_level_bom': fields.boolean('Multi-level BoM'),
196 'active': lambda *a: 1,
197 'product_efficiency': lambda *a: 1.0,
198 'product_qty': lambda *a: 1.0,
199 'product_rounding': lambda *a: 1.0,
200 'type': lambda *a: 'normal',
201 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'mrp.bom', context=c),
202 'multi_level_bom': lambda *a: 0,
206 ('bom_qty_zero', 'CHECK (product_qty>0)', 'All product quantities must be greater than 0.\n' \
207 'You should install the mrp_subproduct module if you want to manage extra products on BoMs !'),
210 def _check_recursion(self, cr, uid, ids):
213 cr.execute('select distinct bom_id from mrp_bom where id =ANY(%s)',(ids,))
214 ids = filter(None, map(lambda x:x[0], cr.fetchall()))
220 (_check_recursion, 'Error ! You can not create recursive BoM.', ['parent_id'])
224 def onchange_product_id(self, cr, uid, ids, product_id, name, context={}):
226 prod=self.pool.get('product.product').browse(cr,uid,[product_id])[0]
227 v = {'product_uom':prod.uom_id.id}
229 v['name'] = prod.name
233 def _bom_find(self, cr, uid, product_id, product_uom, properties=[]):
235 # Why searching on BoM without parent ?
236 cr.execute('select id from mrp_bom where product_id=%s and bom_id is null order by sequence', (product_id,))
237 ids = map(lambda x: x[0], cr.fetchall())
240 for bom in self.pool.get('mrp.bom').browse(cr, uid, ids):
242 for prop_id in bom.property_ids:
243 if prop_id.id in properties:
245 if (prop>max_prop) or ((max_prop==0) and not result):
250 def _bom_explode(self, cr, uid, bom, factor, properties, addthis=False, level=0):
251 factor = factor / (bom.product_efficiency or 1.0)
252 factor = rounding(factor, bom.product_rounding)
253 if factor<bom.product_rounding:
254 factor = bom.product_rounding
258 if bom.type=='phantom' and not bom.bom_lines:
259 newbom = self._bom_find(cr, uid, bom.product_id.id, bom.product_uom.id, properties)
261 res = self._bom_explode(cr, uid, self.browse(cr, uid, [newbom])[0], factor*bom.product_qty, properties, addthis=True, level=level+10)
262 result = result + res[0]
263 result2 = result2 + res[1]
268 if addthis and not bom.bom_lines:
271 'name': bom.product_id.name,
272 'product_id': bom.product_id.id,
273 'product_qty': bom.product_qty * factor,
274 'product_uom': bom.product_uom.id,
275 'product_uos_qty': bom.product_uos and bom.product_uos_qty * factor or False,
276 'product_uos': bom.product_uos and bom.product_uos.id or False,
279 for wc_use in bom.routing_id.workcenter_lines:
280 wc = wc_use.workcenter_id
281 d, m = divmod(factor, wc_use.workcenter_id.capacity_per_cycle)
282 mult = (d + (m and 1.0 or 0.0))
283 cycle = mult * wc_use.cycle_nbr
285 'name': bom.routing_id.name,
286 'workcenter_id': wc.id,
287 'sequence': level+(wc_use.sequence or 0),
289 'hour': float(wc_use.hour_nbr*mult + (wc.time_start+wc.time_stop+cycle*wc.time_cycle) * (wc.time_efficiency or 1.0)),
291 for bom2 in bom.bom_lines:
292 res = self._bom_explode(cr, uid, bom2, factor, properties, addthis=True, level=level+10)
293 result = result + res[0]
294 result2 = result2 + res[1]
295 return result, result2
297 def set_indices(self, cr, uid, ids, context = {}):
298 if not ids or (ids and not ids[0]):
300 res = self.read(cr, uid, ids, ['revision_ids', 'revision_type'])
301 rev_ids = res[0]['revision_ids']
304 bom_rev_obj = self.pool.get('mrp.bom.revision')
305 for rev_id in rev_ids:
306 if res[0]['revision_type'] == 'numeric':
307 bom_rev_obj.write(cr, uid, [rev_id], {'indice' : idx})
309 bom_rev_obj.write(cr, uid, [rev_id], {'indice' : "%c"%(idx+96,)})
315 class mrp_bom_revision(osv.osv):
316 _name = 'mrp.bom.revision'
317 _description = 'Bill of material revisions'
319 'name': fields.char('Modification name', size=64, required=True),
320 'description': fields.text('Description'),
321 'date': fields.date('Modification Date'),
322 'indice': fields.char('Revision', size=16),
323 'last_indice': fields.char('last indice', size=64),
324 'author_id': fields.many2one('res.users', 'Author'),
325 'bom_id': fields.many2one('mrp.bom', 'BoM', select=True),
329 'author_id': lambda x,y,z,c: z,
330 'date': lambda *a: time.strftime('%Y-%m-%d'),
338 return round(f / r) * r
340 class many2many_domain(fields.many2many):
341 def set(self, cr, obj, id, name, values, user=None, context=None):
344 return super(many2many_domain, self).set(cr, obj, id, name, values, user=user,
347 def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
351 move_obj = obj.pool.get('stock.move')
352 for prod in obj.browse(cr, user, ids, context=context):
353 cr.execute("SELECT move_id from mrp_production_move_ids where\
354 production_id=%s" % (prod.id))
355 m_ids = map(lambda x: x[0], cr.fetchall())
356 final = move_obj.search(cr, user, self._domain + [('id', 'in', tuple(m_ids))])
360 class one2many_domain(fields.one2many):
361 def set(self, cr, obj, id, field, values, user=None, context=None):
364 return super(one2many_domain, self).set(cr, obj, id, field, values,
365 user=user, context=context)
367 def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
371 move_obj = obj.pool.get('stock.move')
372 for prod in obj.browse(cr, user, ids, context=context):
373 cr.execute("SELECT id from stock_move where production_id=%s" % (prod.id))
374 m_ids = map(lambda x: x[0], cr.fetchall())
375 final = move_obj.search(cr, user, self._domain + [('id', 'in', tuple(m_ids))])
379 class mrp_production(osv.osv):
380 _name = 'mrp.production'
381 _description = 'Production'
382 _date_name = 'date_planned'
384 def _production_calc(self, cr, uid, ids, prop, unknow_none, context={}):
386 for prod in self.browse(cr, uid, ids, context=context):
391 for wc in prod.workcenter_lines:
392 result[prod.id]['hour_total'] += wc.hour
393 result[prod.id]['cycle_total'] += wc.cycle
396 def _production_date_end(self, cr, uid, ids, prop, unknow_none, context={}):
398 for prod in self.browse(cr, uid, ids, context=context):
399 result[prod.id] = prod.date_planned
402 def _production_date(self, cr, uid, ids, prop, unknow_none, context={}):
404 for prod in self.browse(cr, uid, ids, context=context):
405 result[prod.id] = prod.date_planned[:10]
408 def _ref_calc(self, cr, uid, ids, field_names=None, arg=False, context={}):
410 for f in field_names:
412 res[order_id] = {f:False}
416 'name': fields.char('Reference', size=64, required=True),
417 'origin': fields.char('Source Document', size=64, help="Reference of the document that generated this production order request."),
418 'priority': fields.selection([('0','Not urgent'),('1','Normal'),('2','Urgent'),('3','Very Urgent')], 'Priority'),
420 'product_id': fields.many2one('product.product', 'Product', required=True, domain=[('type','<>','service')]),
421 'product_qty': fields.float('Product Qty', required=True, states={'draft':[('readonly',False)]}, readonly=True),
422 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True, states={'draft':[('readonly',False)]}, readonly=True),
423 'product_uos_qty': fields.float('Product UoS Qty', states={'draft':[('readonly',False)]}, readonly=True),
424 'product_uos': fields.many2one('product.uom', 'Product UoS', states={'draft':[('readonly',False)]}, readonly=True),
426 'location_src_id': fields.many2one('stock.location', 'Raw Materials Location', required=True,
427 help="Location where the system will look for components."),
428 'location_dest_id': fields.many2one('stock.location', 'Finished Products Location', required=True,
429 help="Location where the system will stock the finished products."),
431 'date_planned_end': fields.function(_production_date_end, method=True, type='date', string='Scheduled End'),
432 'date_planned_date': fields.function(_production_date, method=True, type='date', string='Scheduled Date'),
433 'date_planned': fields.datetime('Scheduled date', required=True, select=1),
434 'date_start': fields.datetime('Start Date'),
435 'date_finnished': fields.datetime('End Date'),
437 'bom_id': fields.many2one('mrp.bom', 'Bill of Material', domain=[('bom_id','=',False)]),
438 '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."),
440 'picking_id': fields.many2one('stock.picking', 'Picking list', readonly=True,
441 help="This is the internal picking list that brings the finished product to the production plan"),
442 'move_prod_id': fields.many2one('stock.move', 'Move product', readonly=True),
443 'move_lines': many2many_domain('stock.move', 'mrp_production_move_ids', 'production_id', 'move_id', 'Products to Consumme', domain=[('state','not in', ('done', 'cancel'))]),
444 'move_lines2': many2many_domain('stock.move', 'mrp_production_move_ids', 'production_id', 'move_id', 'Consummed Products', domain=[('state','in', ('done', 'cancel'))]),
445 'move_created_ids': one2many_domain('stock.move', 'production_id', 'Moves Created', domain=[('state','not in', ('done', 'cancel'))]),
446 'move_created_ids2': one2many_domain('stock.move', 'production_id', 'Moves Created', domain=[('state','in', ('done', 'cancel'))]),
447 'product_lines': fields.one2many('mrp.production.product.line', 'production_id', 'Scheduled goods'),
448 'workcenter_lines': fields.one2many('mrp.production.workcenter.line', 'production_id', 'Work Centers Utilisation'),
449 '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,
450 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\'.\
451 \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\'.'),
452 'hour_total': fields.function(_production_calc, method=True, type='float', string='Total Hours', multi='workorder'),
453 'cycle_total': fields.function(_production_calc, method=True, type='float', string='Total Cycles', multi='workorder'),
455 'sale_name': fields.function(_ref_calc, method=True, multi='sale_name', type='char', string='Sale Name', help='Indicate the name of sale order.'),
456 'sale_ref': fields.function(_ref_calc, method=True, multi='sale_ref', type='char', string='Sale Reference', help='Indicate the Customer Reference from sale order.'),
457 'company_id': fields.many2one('res.company','Company',required=True),
460 'priority': lambda *a: '1',
461 'state': lambda *a: 'draft',
462 'date_planned': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
463 'product_qty': lambda *a: 1.0,
464 'name': lambda x,y,z,c: x.pool.get('ir.sequence').get(y,z,'mrp.production') or '/',
465 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'mrp.production', context=c),
467 _order = 'date_planned asc, priority desc';
468 def unlink(self, cr, uid, ids, context=None):
469 productions = self.read(cr, uid, ids, ['state'])
471 for s in productions:
472 if s['state'] in ['draft','cancel']:
473 unlink_ids.append(s['id'])
475 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Production Order(s) which are in %s State!' % s['state']))
476 return osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
478 def copy(self, cr, uid, id, default=None,context=None):
482 'name': self.pool.get('ir.sequence').get(cr, uid, 'mrp.production'),
484 'move_created_ids': [],
487 return super(mrp_production, self).copy(cr, uid, id, default, context)
489 def location_id_change(self, cr, uid, ids, src, dest, context={}):
493 return {'value': {'location_dest_id': src}}
496 def product_id_change(self, cr, uid, ids, product):
499 res = self.pool.get('product.product').read(cr, uid, [product], ['uom_id'])[0]
500 uom = res['uom_id'] and res['uom_id'][0]
501 result = {'product_uom':uom}
502 return {'value':result}
504 def bom_id_change(self, cr, uid, ids, product):
507 res = self.pool.get('mrp.bom').read(cr, uid, [product], ['routing_id'])[0]
508 routing_id = res['routing_id'] and res['routing_id'][0]
509 result = {'routing_id':routing_id}
510 return {'value':result}
512 def action_picking_except(self, cr, uid, ids):
513 self.write(cr, uid, ids, {'state':'picking_except'})
516 def action_compute(self, cr, uid, ids, properties=[]):
518 bom_obj = self.pool.get('mrp.bom')
519 prod_line_obj = self.pool.get('mrp.production.product.line')
520 workcenter_line_obj = self.pool.get('mrp.production.workcenter.line')
521 for production in self.browse(cr, uid, ids):
522 cr.execute('delete from mrp_production_product_line where production_id=%s', (production.id,))
523 cr.execute('delete from mrp_production_workcenter_line where production_id=%s', (production.id,))
524 bom_point = production.bom_id
525 bom_id = production.bom_id.id
527 bom_id = bom_obj._bom_find(cr, uid, production.product_id.id, production.product_uom.id, properties)
529 bom_point = bom_obj.browse(cr, uid, bom_id)
530 routing_id = bom_point.routing_id.id or False
531 self.write(cr, uid, [production.id], {'bom_id': bom_id, 'routing_id': routing_id})
534 raise osv.except_osv(_('Error'), _("Couldn't find bill of material for product"))
536 #if bom_point.routing_id and bom_point.routing_id.location_id:
537 # self.write(cr, uid, [production.id], {'location_src_id': bom_point.routing_id.location_id.id})
539 factor = production.product_qty * production.product_uom.factor / bom_point.product_uom.factor
540 res = bom_obj._bom_explode(cr, uid, bom_point, factor / bom_point.product_qty, properties)
544 line['production_id'] = production.id
545 prod_line_obj.create(cr, uid, line)
546 for line in results2:
547 line['production_id'] = production.id
548 workcenter_line_obj.create(cr, uid, line)
551 def action_cancel(self, cr, uid, ids):
552 move_obj = self.pool.get('stock.move')
553 for production in self.browse(cr, uid, ids):
554 if production.move_created_ids:
555 move_obj.action_cancel(cr, uid, [x.id for x in production.move_created_ids])
556 move_obj.action_cancel(cr, uid, [x.id for x in production.move_lines])
557 self.write(cr, uid, ids, {'state':'cancel'}) #,'move_lines':[(6,0,[])]})
560 #XXX: may be a bug here; lot_lines are unreserved for a few seconds;
561 # between the end of the picking list and the call to this function
562 def action_ready(self, cr, uid, ids):
563 move_obj = self.pool.get('stock.move')
564 self.write(cr, uid, ids, {'state':'ready'})
565 for production in self.browse(cr, uid, ids):
566 if production.move_prod_id:
567 move_obj.write(cr, uid, [production.move_prod_id.id],
568 {'location_id':production.location_dest_id.id})
571 def action_production_end(self, cr, uid, ids):
572 for production in self.browse(cr, uid, ids):
573 self._costs_generate(cr, uid, production)
574 return self.write(cr, uid, ids, {'state': 'done', 'date_finnished': time.strftime('%Y-%m-%d %H:%M:%S')})
576 def test_production_done(self, cr, uid, ids):
578 for production in self.browse(cr, uid, ids):
579 if production.move_lines:
582 if production.move_created_ids:
586 def action_produce(self, cr, uid, production_id, production_qty, production_mode, context=None):
588 To produce final product base on production mode (consume/consume&produce).
589 If Production mode is consume, all stock move lines of raw materials will be done/consumed.
590 If Production mode is consume & produce, all stock move lines of raw materials will be done/consumed
591 and stock move lines of final product will be also done/produced.
593 @param self: The object pointer.
594 @param cr: A database cursor
595 @param uid: ID of the user currently logged in
596 @param production_id: the ID of mrp.production object
597 @param production_qty: specify qty to produce
598 @param production_mode: specify production mode (consume/consume&produce).
603 stock_mov_obj = self.pool.get('stock.move')
604 production = self.browse(cr, uid, production_id)
606 raw_product_todo = []
607 final_product_todo = []
609 if production_mode in ['consume','consume_produce']:
610 # To consume remaining qty of raw materials
611 consumed_products = {}
613 for consumed_product in production.move_lines2:
614 if consumed_product.scraped:
616 if not consumed_products.get(consumed_product.product_id.id, False):
617 consumed_products[consumed_product.product_id.id] = 0
618 consumed_products[consumed_product.product_id.id] += consumed_product.product_qty
620 for produced_product in production.move_created_ids2:
621 if produced_product.scraped:
623 produced_qty += produced_product.product_qty
625 for raw_product in production.move_lines:
626 consumed_qty = consumed_products.get(raw_product.product_id.id, 0)
627 consumed_qty -= produced_qty
628 rest_qty = production_qty - consumed_qty
629 if rest_qty > production.product_qty:
630 rest_qty = production.product_qty
632 stock_mov_obj.action_consume(cr, uid, [raw_product.id], rest_qty, production.location_src_id.id, context=context)
634 if production_mode == 'consume_produce':
635 # To produce remaining qty of final product
636 vals = {'state':'confirmed'}
637 final_product_todo = [x.id for x in production.move_created_ids]
638 stock_mov_obj.write(cr, uid, final_product_todo, vals)
639 produced_products = {}
640 for produced_product in production.move_created_ids2:
641 if produced_product.scraped:
643 if not produced_products.get(produced_product.product_id.id, False):
644 produced_products[produced_product.product_id.id] = 0
645 produced_products[produced_product.product_id.id] += produced_product.product_qty
647 for produce_product in production.move_created_ids:
648 produced_qty = produced_products.get(produce_product.product_id.id, 0)
649 rest_qty = production.product_qty - produced_qty
650 if rest_qty <= production_qty:
651 production_qty = rest_qty
653 stock_mov_obj.action_consume(cr, uid, [produce_product.id], production_qty, production.location_dest_id.id, context=context)
656 for raw_product in production.move_lines2:
658 parent_move_ids = [x.id for x in raw_product.move_history_ids]
659 for final_product in production.move_created_ids2:
660 if final_product.id not in parent_move_ids:
661 new_parent_ids.append(final_product.id)
662 for new_parent_id in new_parent_ids:
663 stock_mov_obj.write(cr, uid, [raw_product.id], {'move_history_ids':[(4,new_parent_id)]})
665 wf_service = netsvc.LocalService("workflow")
666 wf_service.trg_validate(uid, 'mrp.production', production_id, 'button_produce_done', cr)
669 def _costs_generate(self, cr, uid, production):
671 analytic_line_obj = self.pool.get('account.analytic.line')
672 for wc_line in production.workcenter_lines:
673 wc = wc_line.workcenter_id
674 if wc.costs_journal_id and wc.costs_general_account_id:
675 value = wc_line.hour * wc.costs_hour
676 account = wc.costs_hour_account_id.id
677 if value and account:
679 analytic_line_obj.create(cr, uid, {
680 'name': wc_line.name+' (H)',
682 'account_id': account,
683 'general_account_id': wc.costs_general_account_id.id,
684 'journal_id': wc.costs_journal_id.id,
687 if wc.costs_journal_id and wc.costs_general_account_id:
688 value = wc_line.cycle * wc.costs_cycle
689 account = wc.costs_cycle_account_id.id
690 if value and account:
692 analytic_line_obj.create(cr, uid, {
693 'name': wc_line.name+' (C)',
695 'account_id': account,
696 'general_account_id': wc.costs_general_account_id.id,
697 'journal_id': wc.costs_journal_id.id,
702 def action_in_production(self, cr, uid, ids):
704 self.write(cr, uid, ids, {'state': 'in_production','date_start':time.strftime('%Y-%m-%d %H:%M:%S')})
707 def test_if_product(self, cr, uid, ids):
709 for production in self.browse(cr, uid, ids):
710 if not production.product_lines:
711 if not self.action_compute(cr, uid, [production.id]):
715 def _get_auto_picking(self, cr, uid, production):
718 def action_confirm(self, cr, uid, ids):
721 seq_obj = self.pool.get('ir.sequence')
722 pick_obj = self.pool.get('stock.picking')
723 move_obj = self.pool.get('stock.move')
724 proc_obj = self.pool.get('mrp.procurement')
725 for production in self.browse(cr, uid, ids):
726 if not production.product_lines:
727 self.action_compute(cr, uid, [production.id])
728 production = self.browse(cr, uid, [production.id])[0]
730 pick_type = 'internal'
732 if production.bom_id.routing_id and production.bom_id.routing_id.location_id:
733 routing_loc = production.bom_id.routing_id.location_id
734 if routing_loc.usage<>'internal':
736 address_id = routing_loc.address_id and routing_loc.address_id.id or False
737 routing_loc = routing_loc.id
738 pick_name = seq_obj.get(cr, uid, 'stock.picking.'+pick_type)
739 picking_id = pick_obj.create(cr, uid, {
741 'origin': (production.origin or '').split(':')[0] +':'+production.name,
745 'address_id': address_id,
746 'auto_picking': self._get_auto_picking(cr, uid, production),
747 'company_id': production.company_id.id,
750 source = production.product_id.product_tmpl_id.property_stock_production.id
752 'name':'PROD:'+production.name,
753 'date_planned': production.date_planned,
754 'product_id': production.product_id.id,
755 'product_qty': production.product_qty,
756 'product_uom': production.product_uom.id,
757 'product_uos_qty': production.product_uos and production.product_uos_qty or False,
758 'product_uos': production.product_uos and production.product_uos.id or False,
759 'location_id': source,
760 'location_dest_id': production.location_dest_id.id,
761 'move_dest_id': production.move_prod_id.id,
763 'company_id': production.company_id.id,
765 res_final_id = move_obj.create(cr, uid, data)
767 self.write(cr, uid, [production.id], {'move_created_ids': [(6, 0, [res_final_id])]})
769 for line in production.product_lines:
771 newdate = production.date_planned
772 if line.product_id.type in ('product', 'consu'):
773 res_dest_id = move_obj.create(cr, uid, {
774 'name':'PROD:'+production.name,
775 'date_planned': production.date_planned,
776 'product_id': line.product_id.id,
777 'product_qty': line.product_qty,
778 'product_uom': line.product_uom.id,
779 'product_uos_qty': line.product_uos and line.product_uos_qty or False,
780 'product_uos': line.product_uos and line.product_uos.id or False,
781 'location_id': routing_loc or production.location_src_id.id,
782 'location_dest_id': source,
783 'move_dest_id': res_final_id,
785 'company_id': production.company_id.id,
787 moves.append(res_dest_id)
788 move_id = move_obj.create(cr, uid, {
789 'name':'PROD:'+production.name,
790 'picking_id':picking_id,
791 'product_id': line.product_id.id,
792 'product_qty': line.product_qty,
793 'product_uom': line.product_uom.id,
794 'product_uos_qty': line.product_uos and line.product_uos_qty or False,
795 'product_uos': line.product_uos and line.product_uos.id or False,
796 'date_planned': newdate,
797 'move_dest_id': res_dest_id,
798 'location_id': production.location_src_id.id,
799 'location_dest_id': routing_loc or production.location_src_id.id,
801 'company_id': production.company_id.id,
803 proc_id = proc_obj.create(cr, uid, {
804 'name': (production.origin or '').split(':')[0] + ':' + production.name,
805 'origin': (production.origin or '').split(':')[0] + ':' + production.name,
806 'date_planned': newdate,
807 'product_id': line.product_id.id,
808 'product_qty': line.product_qty,
809 'product_uom': line.product_uom.id,
810 'product_uos_qty': line.product_uos and line.product_qty or False,
811 'product_uos': line.product_uos and line.product_uos.id or False,
812 'location_id': production.location_src_id.id,
813 'procure_method': line.product_id.procure_method,
815 'company_id': production.company_id.id,
817 wf_service = netsvc.LocalService("workflow")
818 wf_service.trg_validate(uid, 'mrp.procurement', proc_id, 'button_confirm', cr)
819 proc_ids.append(proc_id)
820 wf_service = netsvc.LocalService("workflow")
821 wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
822 self.write(cr, uid, [production.id], {'picking_id':picking_id, 'move_lines': [(6,0,moves)], 'state':'confirmed'})
825 def force_production(self, cr, uid, ids, *args):
826 pick_obj = self.pool.get('stock.picking')
827 pick_obj.force_assign(cr, uid, [prod.picking_id.id for prod in self.browse(cr, uid, ids)])
832 class mrp_production_workcenter_line(osv.osv):
833 _name = 'mrp.production.workcenter.line'
834 _description = 'Work Orders'
837 'name': fields.char('Work Order', size=64, required=True),
838 'workcenter_id': fields.many2one('mrp.workcenter', 'Work Center', required=True),
839 'cycle': fields.float('Nbr of cycles', digits=(16,2)),
840 'hour': fields.float('Nbr of hours', digits=(16,2)),
841 'sequence': fields.integer('Sequence', required=True, help="Gives the sequence order when displaying a list of work orders."),
842 'production_id': fields.many2one('mrp.production', 'Production Order', select=True, ondelete='cascade'),
845 'sequence': lambda *a: 1,
846 'hour': lambda *a: 0,
847 'cycle': lambda *a: 0,
849 mrp_production_workcenter_line()
851 class mrp_production_product_line(osv.osv):
852 _name = 'mrp.production.product.line'
853 _description = 'Production scheduled products'
855 'name': fields.char('Name', size=64, required=True),
856 'product_id': fields.many2one('product.product', 'Product', required=True),
857 'product_qty': fields.float('Product Qty', required=True),
858 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True),
859 'product_uos_qty': fields.float('Product UOS Qty'),
860 'product_uos': fields.many2one('product.uom', 'Product UOS'),
861 'production_id': fields.many2one('mrp.production', 'Production Order', select=True),
863 mrp_production_product_line()
865 # ------------------------------------------------------------------
867 # ------------------------------------------------------------------
869 # Produce, Buy or Find products and place a move
870 # then wizard for picking lists & move
872 class mrp_procurement(osv.osv):
873 _name = "mrp.procurement"
874 _description = "Procurement"
875 _order = 'priority,date_planned'
877 'name': fields.char('Reason', size=64, required=True, help='Requisition name.'),
878 'origin': fields.char('Source Document', size=64,
879 help="Reference of the document that created this Requisition.\n"
880 "This is automatically completed by Open ERP."),
881 'priority': fields.selection([('0','Not urgent'),('1','Normal'),('2','Urgent'),('3','Very Urgent')], 'Priority', required=True),
882 'date_planned': fields.datetime('Scheduled date', required=True),
883 'date_close': fields.datetime('Date Closed'),
884 'product_id': fields.many2one('product.product', 'Product', required=True, states={'draft':[('readonly',False)]}, readonly=True),
885 'product_qty': fields.float('Quantity', required=True, states={'draft':[('readonly',False)]}, readonly=True),
886 'product_uom': fields.many2one('product.uom', 'Product UoM', required=True, states={'draft':[('readonly',False)]}, readonly=True),
887 'product_uos_qty': fields.float('UoS Quantity', states={'draft':[('readonly',False)]}, readonly=True),
888 'product_uos': fields.many2one('product.uom', 'Product UoS', states={'draft':[('readonly',False)]}, readonly=True),
889 'move_id': fields.many2one('stock.move', 'Reservation', ondelete='set null'),
891 'bom_id': fields.many2one('mrp.bom', 'BoM', ondelete='cascade', select=True),
893 'close_move': fields.boolean('Close Move at end', required=True),
894 'location_id': fields.many2one('stock.location', 'Location', required=True, states={'draft':[('readonly',False)]}, readonly=True),
895 'procure_method': fields.selection([('make_to_stock','from stock'),('make_to_order','on order')], 'Procurement Method', states={'draft':[('readonly',False)], 'confirmed':[('readonly',False)]},
896 readonly=True, required=True, help="If you encode manually a Procurement, you probably want to use" \
897 " a make to order method."),
899 'purchase_id': fields.many2one('purchase.order', 'Purchase Order'),
900 'note': fields.text('Note'),
902 'property_ids': fields.many2many('mrp.property', 'mrp_procurement_property_rel', 'procurement_id','property_id', 'Properties'),
904 'message': fields.char('Latest error', size=64, help="Exception occurred while computing procurement orders."),
905 'state': fields.selection([
907 ('confirmed','Confirmed'),
908 ('exception','Exception'),
909 ('running','Running'),
913 ('waiting','Waiting')], 'State', required=True,
914 help='When a procurement is created the state is set to \'Draft\'.\n If the procurement is confirmed, the state is set to \'Confirmed\'.\
915 \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.'),
916 'note' : fields.text('Note'),
917 'company_id': fields.many2one('res.company','Company',required=True),
920 'state': lambda *a: 'draft',
921 'priority': lambda *a: '1',
922 'date_planned': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
923 'close_move': lambda *a: 0,
924 'procure_method': lambda *a: 'make_to_order',
925 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'mrp.procurement', context=c)
928 def unlink(self, cr, uid, ids, context=None):
929 procurements = self.read(cr, uid, ids, ['state'])
931 for s in procurements:
932 if s['state'] in ['draft','cancel']:
933 unlink_ids.append(s['id'])
935 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Requisition Order(s) which are in %s State!' % s['state']))
936 return osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
938 def onchange_product_id(self, cr, uid, ids, product_id, context={}):
940 w=self.pool.get('product.product').browse(cr,uid,product_id, context)
942 'product_uom':w.uom_id.id,
943 'product_uos':w.uos_id and w.uos_id.id or w.uom_id.id
948 def check_product(self, cr, uid, ids):
949 for procurement in self.browse(cr, uid, ids):
950 if procurement.product_id.type in ('product', 'consu'):
954 def check_move_cancel(self, cr, uid, ids, context={}):
957 for procurement in self.browse(cr, uid, ids, context):
958 if procurement.move_id:
960 if not procurement.move_id.state=='cancel':
964 def check_move_done(self, cr, uid, ids, context={}):
966 for proc in self.browse(cr, uid, ids, context):
968 if not proc.move_id.state=='done':
973 # This method may be overrided by objects that override mrp.procurment
974 # for computing their own purpose
976 def _quantity_compute_get(self, cr, uid, proc, context={}):
977 if proc.product_id.type=='product':
978 if proc.move_id.product_uos:
979 return proc.move_id.product_uos_qty
982 def _uom_compute_get(self, cr, uid, proc, context={}):
983 if proc.product_id.type=='product':
984 if proc.move_id.product_uos:
985 return proc.move_id.product_uos.id
989 # Return the quantity of product shipped/produced/served, wich may be
990 # different from the planned quantity
992 def quantity_get(self, cr, uid, id, context={}):
993 proc = self.browse(cr, uid, id, context)
994 result = self._quantity_compute_get(cr, uid, proc, context)
996 result = proc.product_qty
999 def uom_get(self, cr, uid, id, context=None):
1000 proc = self.browse(cr, uid, id, context)
1001 result = self._uom_compute_get(cr, uid, proc, context)
1003 result = proc.product_uom.id
1006 def check_waiting(self, cr, uid, ids, context=[]):
1007 for procurement in self.browse(cr, uid, ids, context=context):
1008 if procurement.move_id and procurement.move_id.state=='auto':
1012 def check_produce_service(self, cr, uid, procurement, context=[]):
1015 def check_produce_product(self, cr, uid, procurement, context=[]):
1016 properties = [x.id for x in procurement.property_ids]
1017 bom_id = self.pool.get('mrp.bom')._bom_find(cr, uid, procurement.product_id.id, procurement.product_uom.id, properties)
1019 cr.execute('update mrp_procurement set message=%s where id=%s', (_('No BoM defined for this product !'), procurement.id))
1023 def check_make_to_stock(self, cr, uid, ids, context={}):
1025 for procurement in self.browse(cr, uid, ids, context=context):
1026 if procurement.product_id.type=='service':
1027 ok = ok and self._check_make_to_stock_service(cr, uid, procurement, context)
1029 ok = ok and self._check_make_to_stock_product(cr, uid, procurement, context)
1032 def check_produce(self, cr, uid, ids, context={}):
1034 user = self.pool.get('res.users').browse(cr, uid, uid)
1035 for procurement in self.browse(cr, uid, ids):
1036 if procurement.product_id.product_tmpl_id.supply_method<>'produce':
1037 if procurement.product_id.seller_ids:
1038 partner = procurement.product_id.seller_ids[0].name
1039 if user.company_id and user.company_id.partner_id:
1040 if partner.id == user.company_id.partner_id.id:
1043 if procurement.product_id.product_tmpl_id.type=='service':
1044 res = res and self.check_produce_service(cr, uid, procurement, context)
1046 res = res and self.check_produce_product(cr, uid, procurement, context)
1051 def check_buy(self, cr, uid, ids):
1052 user = self.pool.get('res.users').browse(cr, uid, uid)
1053 partner_obj = self.pool.get('res.partner')
1054 for procurement in self.browse(cr, uid, ids):
1055 if procurement.product_id.product_tmpl_id.supply_method<>'buy':
1057 if not procurement.product_id.seller_ids:
1058 cr.execute('update mrp_procurement set message=%s where id=%s', (_('No supplier defined for this product !'), procurement.id))
1060 partner = procurement.product_id.seller_ids[0].name
1061 if user.company_id and user.company_id.partner_id:
1062 if partner.id == user.company_id.partner_id.id:
1064 address_id = partner_obj.address_get(cr, uid, [partner.id], ['delivery'])['delivery']
1066 cr.execute('update mrp_procurement set message=%s where id=%s', (_('No address defined for the supplier'), procurement.id))
1070 def test_cancel(self, cr, uid, ids):
1071 for record in self.browse(cr, uid, ids):
1072 if record.move_id and record.move_id.state=='cancel':
1076 def action_confirm(self, cr, uid, ids, context={}):
1077 move_obj = self.pool.get('stock.move')
1078 for procurement in self.browse(cr, uid, ids):
1079 if procurement.product_qty <= 0.00:
1080 raise osv.except_osv(_('Data Insufficient !'), _('Please check the Quantity of Requisition Order(s), it should not be less than 1!'))
1081 if procurement.product_id.type in ('product', 'consu'):
1082 if not procurement.move_id:
1083 source = procurement.location_id.id
1084 if procurement.procure_method=='make_to_order':
1085 source = procurement.product_id.product_tmpl_id.property_stock_procurement.id
1086 id = move_obj.create(cr, uid, {
1087 'name': 'PROC:'+procurement.name,
1088 'location_id': source,
1089 'location_dest_id': procurement.location_id.id,
1090 'product_id': procurement.product_id.id,
1091 'product_qty':procurement.product_qty,
1092 'product_uom': procurement.product_uom.id,
1093 'date_planned': procurement.date_planned,
1094 'state':'confirmed',
1095 'company_id': procurement.company_id.id,
1097 self.write(cr, uid, [procurement.id], {'move_id': id, 'close_move':1})
1100 if procurement.procure_method=='make_to_stock' and procurement.move_id.state in ('waiting',):
1101 id = move_obj.write(cr, uid, [procurement.move_id.id], {'state':'confirmed'})
1102 self.write(cr, uid, ids, {'state':'confirmed','message':''})
1105 def action_move_assigned(self, cr, uid, ids, context={}):
1106 self.write(cr, uid, ids, {'state':'running','message':_('from stock: products assigned.')})
1109 def _check_make_to_stock_service(self, cr, uid, procurement, context={}):
1112 def _check_make_to_stock_product(self, cr, uid, procurement, context={}):
1114 if procurement.move_id:
1115 id = procurement.move_id.id
1116 if not (procurement.move_id.state in ('done','assigned','cancel')):
1117 ok = ok and self.pool.get('stock.move').action_assign(cr, uid, [id])
1118 cr.execute('select count(id) from stock_warehouse_orderpoint where product_id=%s', (procurement.product_id.id,))
1119 if not cr.fetchone()[0]:
1120 cr.execute('update mrp_procurement set message=%s where id=%s', (_('from stock and no minimum orderpoint rule defined'), procurement.id))
1123 def action_produce_assign_service(self, cr, uid, ids, context={}):
1124 for procurement in self.browse(cr, uid, ids):
1125 self.write(cr, uid, [procurement.id], {'state':'running'})
1128 def action_produce_assign_product(self, cr, uid, ids, context={}):
1130 This is action which call from workflow to assign production order to procuments
1133 res = self.make_mo(cr, uid, ids, context=context)
1135 return len(res) and res[0] or 0 #TO CHECK: why workflow is generated error if return not integer value
1137 def make_mo(self, cr, uid, ids, context={}):
1139 Make Manufecturing(production) order from procurement
1141 @return : New created Production Orders procurement wise
1144 company = self.pool.get('res.users').browse(cr, uid, uid, context).company_id
1145 production_obj = self.pool.get('mrp.production')
1146 move_obj = self.pool.get('stock.move')
1147 wf_service = netsvc.LocalService("workflow")
1148 for procurement in self.browse(cr, uid, ids):
1149 res_id = procurement.move_id.id
1150 loc_id = procurement.location_id.id
1151 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)
1152 newdate = newdate - DateTime.RelativeDateTime(days=company.manufacturing_lead)
1153 produce_id = production_obj.create(cr, uid, {
1154 'origin': procurement.origin,
1155 'product_id': procurement.product_id.id,
1156 'product_qty': procurement.product_qty,
1157 'product_uom': procurement.product_uom.id,
1158 'product_uos_qty': procurement.product_uos and procurement.product_uos_qty or False,
1159 'product_uos': procurement.product_uos and procurement.product_uos.id or False,
1160 'location_src_id': procurement.location_id.id,
1161 'location_dest_id': procurement.location_id.id,
1162 'bom_id': procurement.bom_id and procurement.bom_id.id or False,
1163 'date_planned': newdate.strftime('%Y-%m-%d %H:%M:%S'),
1164 'move_prod_id': res_id,
1165 'company_id': procurement.company_id.id,
1167 res[procurement.id] = produce_id
1168 self.write(cr, uid, [procurement.id], {'state':'running'})
1169 bom_result = production_obj.action_compute(cr, uid,
1170 [produce_id], properties=[x.id for x in procurement.property_ids])
1171 wf_service.trg_validate(uid, 'mrp.production', produce_id, 'button_confirm', cr)
1172 move_obj.write(cr, uid, [res_id],
1173 {'location_id':procurement.location_id.id})
1176 def action_po_assign(self, cr, uid, ids, context={}):
1178 This is action which call from workflow to assign purchase order to procuments
1181 res = self.make_po(cr, uid, ids, context=context)
1183 return len(res) and res[0] or 0 #TO CHECK: why workflow is generated error if return not integer value
1185 def make_po(self, cr, uid, ids, context={}):
1187 Make purchase order from procurement
1189 @return : New created Purchase Orders procurement wise
1192 company = self.pool.get('res.users').browse(cr, uid, uid, context).company_id
1193 partner_obj = self.pool.get('res.partner')
1194 uom_obj = self.pool.get('product.uom')
1195 pricelist_obj = self.pool.get('product.pricelist')
1196 prod_obj = self.pool.get('product.product')
1197 acc_pos_obj = self.pool.get('account.fiscal.position')
1198 po_obj = self.pool.get('purchase.order')
1199 for procurement in self.browse(cr, uid, ids):
1200 res_id = procurement.move_id.id
1201 partner = procurement.product_id.seller_ids[0].name
1202 partner_id = partner.id
1203 address_id = partner_obj.address_get(cr, uid, [partner_id], ['delivery'])['delivery']
1204 pricelist_id = partner.property_product_pricelist_purchase.id
1206 uom_id = procurement.product_id.uom_po_id.id
1208 qty = uom_obj._compute_qty(cr, uid, procurement.product_uom.id, procurement.product_qty, uom_id)
1209 if procurement.product_id.seller_ids[0].qty:
1210 qty=max(qty,procurement.product_id.seller_ids[0].qty)
1212 price = pricelist_obj.price_get(cr, uid, [pricelist_id], procurement.product_id.id, qty, False, {'uom': uom_id})[pricelist_id]
1214 newdate = DateTime.strptime(procurement.date_planned, '%Y-%m-%d %H:%M:%S')
1215 newdate = newdate - DateTime.RelativeDateTime(days=company.po_lead)
1216 newdate = newdate - procurement.product_id.seller_ids[0].delay
1218 #Passing partner_id to context for purchase order line integrity of Line name
1219 context.update({'lang':partner.lang, 'partner_id':partner_id})
1221 product = prod_obj.browse(cr,uid,procurement.product_id.id,context=context)
1224 'name': product.partner_ref,
1226 'product_id': procurement.product_id.id,
1227 'product_uom': uom_id,
1228 'price_unit': price,
1229 'date_planned': newdate.strftime('%Y-%m-%d %H:%M:%S'),
1230 'move_dest_id': res_id,
1231 'notes':product.description_purchase,
1234 taxes_ids = procurement.product_id.product_tmpl_id.supplier_taxes_id
1235 taxes = acc_pos_obj.map_tax(cr, uid, partner.property_account_position, taxes_ids)
1237 'taxes_id':[(6,0,taxes)]
1239 purchase_id = po_obj.create(cr, uid, {
1240 'origin': procurement.origin,
1241 'partner_id': partner_id,
1242 'partner_address_id': address_id,
1243 'location_id': procurement.location_id.id,
1244 'pricelist_id': pricelist_id,
1245 'order_line': [(0,0,line)],
1246 'company_id': procurement.company_id.id,
1247 'fiscal_position': partner.property_account_position and partner.property_account_position.id or False
1249 res[procurement.id] = purchase_id
1250 self.write(cr, uid, [procurement.id], {'state':'running', 'purchase_id':purchase_id})
1253 def action_cancel(self, cr, uid, ids):
1256 move_obj = self.pool.get('stock.move')
1257 for proc in self.browse(cr, uid, ids):
1259 if proc.move_id.state not in ('done','cancel'):
1260 todo2.append(proc.move_id.id)
1262 if proc.move_id and proc.move_id.state=='waiting':
1263 todo.append(proc.move_id.id)
1265 move_obj.action_cancel(cr, uid, todo2)
1267 move_obj.write(cr, uid, todo, {'state':'assigned'})
1268 self.write(cr, uid, ids, {'state':'cancel'})
1269 wf_service = netsvc.LocalService("workflow")
1271 wf_service.trg_trigger(uid, 'mrp.procurement', id, cr)
1274 def action_check_finnished(self, cr, uid, ids):
1275 return self.check_move_done(cr, uid, ids)
1277 def action_check(self, cr, uid, ids):
1279 for procurement in self.browse(cr, uid, ids):
1280 if procurement.move_id.state=='assigned' or procurement.move_id.state=='done':
1281 self.action_done(cr, uid, [procurement.id])
1285 def action_ready(self, cr, uid, ids):
1286 res = self.write(cr, uid, ids, {'state':'ready'})
1289 def action_done(self, cr, uid, ids):
1290 move_obj = self.pool.get('stock.move')
1291 for procurement in self.browse(cr, uid, ids):
1292 if procurement.move_id:
1293 if procurement.close_move and (procurement.move_id.state <> 'done'):
1294 move_obj.action_done(cr, uid, [procurement.move_id.id])
1295 res = self.write(cr, uid, ids, {'state':'done', 'date_close':time.strftime('%Y-%m-%d')})
1296 wf_service = netsvc.LocalService("workflow")
1298 wf_service.trg_trigger(uid, 'mrp.procurement', id, cr)
1301 def run_scheduler(self, cr, uid, automatic=False, use_new_cursor=False, context=None):
1303 use_new_cursor: False or the dbname
1307 self._procure_confirm(cr, uid, use_new_cursor=use_new_cursor, context=context)
1308 self._procure_orderpoint_confirm(cr, uid, automatic=automatic,\
1309 use_new_cursor=use_new_cursor, context=context)
1311 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: