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 for rev_id in rev_ids:
305 if res[0]['revision_type'] == 'numeric':
306 self.pool.get('mrp.bom.revision').write(cr, uid, [rev_id], {'indice' : idx})
308 self.pool.get('mrp.bom.revision').write(cr, uid, [rev_id], {'indice' : "%c"%(idx+96,)})
314 class mrp_bom_revision(osv.osv):
315 _name = 'mrp.bom.revision'
316 _description = 'Bill of material revisions'
318 'name': fields.char('Modification name', size=64, required=True),
319 'description': fields.text('Description'),
320 'date': fields.date('Modification Date'),
321 'indice': fields.char('Revision', size=16),
322 'last_indice': fields.char('last indice', size=64),
323 'author_id': fields.many2one('res.users', 'Author'),
324 'bom_id': fields.many2one('mrp.bom', 'BoM', select=True),
328 'author_id': lambda x,y,z,c: z,
329 'date': lambda *a: time.strftime('%Y-%m-%d'),
337 return round(f / r) * r
339 class many2many_domain(fields.many2many):
340 def set(self, cr, obj, id, name, values, user=None, context=None):
343 return super(many2many_domain, self).set(cr, obj, id, name, values, user=user,
346 def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
350 move_obj = obj.pool.get('stock.move')
351 for prod in obj.browse(cr, user, ids, context=context):
352 cr.execute("SELECT move_id from mrp_production_move_ids where\
353 production_id=%s" % (prod.id))
354 m_ids = map(lambda x: x[0], cr.fetchall())
355 final = move_obj.search(cr, user, self._domain + [('id', 'in', tuple(m_ids))])
359 class one2many_domain(fields.one2many):
360 def set(self, cr, obj, id, field, values, user=None, context=None):
363 return super(one2many_domain, self).set(cr, obj, id, field, values,
364 user=user, context=context)
366 def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
370 move_obj = obj.pool.get('stock.move')
371 for prod in obj.browse(cr, user, ids, context=context):
372 cr.execute("SELECT id from stock_move where production_id=%s" % (prod.id))
373 m_ids = map(lambda x: x[0], cr.fetchall())
374 final = move_obj.search(cr, user, self._domain + [('id', 'in', tuple(m_ids))])
378 class mrp_production(osv.osv):
379 _name = 'mrp.production'
380 _description = 'Production'
381 _date_name = 'date_planned'
383 def _production_calc(self, cr, uid, ids, prop, unknow_none, context={}):
385 for prod in self.browse(cr, uid, ids, context=context):
390 for wc in prod.workcenter_lines:
391 result[prod.id]['hour_total'] += wc.hour
392 result[prod.id]['cycle_total'] += wc.cycle
395 def _production_date_end(self, cr, uid, ids, prop, unknow_none, context={}):
397 for prod in self.browse(cr, uid, ids, context=context):
398 result[prod.id] = prod.date_planned
401 def _production_date(self, cr, uid, ids, prop, unknow_none, context={}):
403 for prod in self.browse(cr, uid, ids, context=context):
404 result[prod.id] = prod.date_planned[:10]
407 def _ref_calc(self, cr, uid, ids, field_names=None, arg=False, context={}):
409 for f in field_names:
411 res[order_id] = {f:False}
415 'name': fields.char('Reference', size=64, required=True),
416 'origin': fields.char('Source Document', size=64, help="Reference of the document that generated this production order request."),
417 'priority': fields.selection([('0','Not urgent'),('1','Normal'),('2','Urgent'),('3','Very Urgent')], 'Priority'),
419 'product_id': fields.many2one('product.product', 'Product', required=True, domain=[('type','<>','service')]),
420 'product_qty': fields.float('Product Qty', required=True, states={'draft':[('readonly',False)]}, readonly=True),
421 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True, states={'draft':[('readonly',False)]}, readonly=True),
422 'product_uos_qty': fields.float('Product UoS Qty', states={'draft':[('readonly',False)]}, readonly=True),
423 'product_uos': fields.many2one('product.uom', 'Product UoS', states={'draft':[('readonly',False)]}, readonly=True),
425 'location_src_id': fields.many2one('stock.location', 'Raw Materials Location', required=True,
426 help="Location where the system will look for components."),
427 'location_dest_id': fields.many2one('stock.location', 'Finished Products Location', required=True,
428 help="Location where the system will stock the finished products."),
430 'date_planned_end': fields.function(_production_date_end, method=True, type='date', string='Scheduled End'),
431 'date_planned_date': fields.function(_production_date, method=True, type='date', string='Scheduled Date'),
432 'date_planned': fields.datetime('Scheduled date', required=True, select=1),
433 'date_start': fields.datetime('Start Date'),
434 'date_finnished': fields.datetime('End Date'),
436 'bom_id': fields.many2one('mrp.bom', 'Bill of Material', domain=[('bom_id','=',False)]),
437 '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."),
439 'picking_id': fields.many2one('stock.picking', 'Picking list', readonly=True,
440 help="This is the internal picking list that brings the finished product to the production plan"),
441 'move_prod_id': fields.many2one('stock.move', 'Move product', readonly=True),
442 'move_lines': many2many_domain('stock.move', 'mrp_production_move_ids', 'production_id', 'move_id', 'Products to Consumme', domain=[('state','not in', ('done', 'cancel'))]),
443 'move_lines2': many2many_domain('stock.move', 'mrp_production_move_ids', 'production_id', 'move_id', 'Consummed Products', domain=[('state','in', ('done', 'cancel'))]),
444 'move_created_ids': one2many_domain('stock.move', 'production_id', 'Moves Created', domain=[('state','not in', ('done', 'cancel'))]),
445 'move_created_ids2': one2many_domain('stock.move', 'production_id', 'Moves Created', domain=[('state','in', ('done', 'cancel'))]),
446 'product_lines': fields.one2many('mrp.production.product.line', 'production_id', 'Scheduled goods'),
447 'workcenter_lines': fields.one2many('mrp.production.workcenter.line', 'production_id', 'Work Centers Utilisation'),
448 '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,
449 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\'.\
450 \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\'.'),
451 'hour_total': fields.function(_production_calc, method=True, type='float', string='Total Hours', multi='workorder'),
452 'cycle_total': fields.function(_production_calc, method=True, type='float', string='Total Cycles', multi='workorder'),
454 'sale_name': fields.function(_ref_calc, method=True, multi='sale_name', type='char', string='Sale Name', help='Indicate the name of sale order.'),
455 'sale_ref': fields.function(_ref_calc, method=True, multi='sale_ref', type='char', string='Sale Reference', help='Indicate the Customer Reference from sale order.'),
456 'company_id': fields.many2one('res.company','Company',required=True),
459 'priority': lambda *a: '1',
460 'state': lambda *a: 'draft',
461 'date_planned': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
462 'product_qty': lambda *a: 1.0,
463 'name': lambda x,y,z,c: x.pool.get('ir.sequence').get(y,z,'mrp.production') or '/',
464 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'mrp.production', context=c),
466 _order = 'date_planned asc, priority desc';
467 def unlink(self, cr, uid, ids, context=None):
468 productions = self.read(cr, uid, ids, ['state'])
470 for s in productions:
471 if s['state'] in ['draft','cancel']:
472 unlink_ids.append(s['id'])
474 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Production Order(s) which are in %s State!' % s['state']))
475 return osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
477 def copy(self, cr, uid, id, default=None,context=None):
481 'name': self.pool.get('ir.sequence').get(cr, uid, 'mrp.production'),
483 'move_created_ids': [],
486 return super(mrp_production, self).copy(cr, uid, id, default, context)
488 def location_id_change(self, cr, uid, ids, src, dest, context={}):
492 return {'value': {'location_dest_id': src}}
495 def product_id_change(self, cr, uid, ids, product):
498 res = self.pool.get('product.product').read(cr, uid, [product], ['uom_id'])[0]
499 uom = res['uom_id'] and res['uom_id'][0]
500 result = {'product_uom':uom}
501 return {'value':result}
503 def bom_id_change(self, cr, uid, ids, product):
506 res = self.pool.get('mrp.bom').read(cr, uid, [product], ['routing_id'])[0]
507 routing_id = res['routing_id'] and res['routing_id'][0]
508 result = {'routing_id':routing_id}
509 return {'value':result}
511 def action_picking_except(self, cr, uid, ids):
512 self.write(cr, uid, ids, {'state':'picking_except'})
515 def action_compute(self, cr, uid, ids, properties=[]):
517 for production in self.browse(cr, uid, ids):
518 cr.execute('delete from mrp_production_product_line where production_id=%s', (production.id,))
519 cr.execute('delete from mrp_production_workcenter_line where production_id=%s', (production.id,))
520 bom_point = production.bom_id
521 bom_id = production.bom_id.id
523 bom_id = self.pool.get('mrp.bom')._bom_find(cr, uid, production.product_id.id, production.product_uom.id, properties)
525 bom_point = self.pool.get('mrp.bom').browse(cr, uid, bom_id)
526 routing_id = bom_point.routing_id.id or False
527 self.write(cr, uid, [production.id], {'bom_id': bom_id, 'routing_id': routing_id})
530 raise osv.except_osv(_('Error'), _("Couldn't find bill of material for product"))
532 #if bom_point.routing_id and bom_point.routing_id.location_id:
533 # self.write(cr, uid, [production.id], {'location_src_id': bom_point.routing_id.location_id.id})
535 factor = production.product_qty * production.product_uom.factor / bom_point.product_uom.factor
536 res = self.pool.get('mrp.bom')._bom_explode(cr, uid, bom_point, factor / bom_point.product_qty, properties)
540 line['production_id'] = production.id
541 self.pool.get('mrp.production.product.line').create(cr, uid, line)
542 for line in results2:
543 line['production_id'] = production.id
544 self.pool.get('mrp.production.workcenter.line').create(cr, uid, line)
547 def action_cancel(self, cr, uid, ids):
548 for production in self.browse(cr, uid, ids):
549 if production.move_created_ids:
550 self.pool.get('stock.move').action_cancel(cr, uid, [x.id for x in production.move_created_ids])
551 self.pool.get('stock.move').action_cancel(cr, uid, [x.id for x in production.move_lines])
552 self.write(cr, uid, ids, {'state':'cancel'}) #,'move_lines':[(6,0,[])]})
555 #XXX: may be a bug here; lot_lines are unreserved for a few seconds;
556 # between the end of the picking list and the call to this function
557 def action_ready(self, cr, uid, ids):
558 self.write(cr, uid, ids, {'state':'ready'})
559 for production in self.browse(cr, uid, ids):
560 if production.move_prod_id:
561 self.pool.get('stock.move').write(cr, uid, [production.move_prod_id.id],
562 {'location_id':production.location_dest_id.id})
565 def action_production_end(self, cr, uid, ids):
566 for production in self.browse(cr, uid, ids):
567 self._costs_generate(cr, uid, production)
568 return self.write(cr, uid, ids, {'state': 'done', 'date_finnished': time.strftime('%Y-%m-%d %H:%M:%S')})
570 def test_production_done(self, cr, uid, ids):
572 for production in self.browse(cr, uid, ids):
573 if production.move_lines:
576 if production.move_created_ids:
580 def action_produce(self, cr, uid, production_id, production_qty, production_mode, context=None):
582 @summary: To produce final product base on production mode (consume/consume&produce).
583 If Production mode is consume, all stock move lines of raw materials will be done/consumed.
584 If Production mode is consume & produce, all stock move lines of raw materials will be done/consumed
585 and stock move lines of final product will be also done/produced.
587 @param self: The object pointer.
588 @param cr: A database cursor
589 @param uid: ID of the user currently logged in
590 @param production_id: the ID of mrp.production object
591 @param production_qty: specify qty to produce
592 @param production_mode: specify production mode (consume/consume&produce).
597 stock_mov_obj = self.pool.get('stock.move')
598 production = self.browse(cr, uid, production_id)
600 raw_product_todo = []
601 final_product_todo = []
603 if production_mode in ['consume','consume_produce']:
604 # To consume remaining qty of raw materials
605 consumed_products = {}
607 for consumed_product in production.move_lines2:
608 if not consumed_products.get(consumed_product.product_id.id, False):
609 consumed_products[consumed_product.product_id.id] = 0
610 consumed_products[consumed_product.product_id.id] += consumed_product.product_qty
612 for produced_product in production.move_created_ids2:
613 produced_qty += produced_product.product_qty
615 for raw_product in production.move_lines:
616 consumed_qty = consumed_products.get(raw_product.product_id.id, 0)
617 consumed_qty -= produced_qty
618 rest_qty = production_qty - consumed_qty
619 if rest_qty > production.product_qty:
620 rest_qty = production.product_qty
622 stock_mov_obj.action_consume(cr, uid, [raw_product.id], rest_qty, production.location_src_id.id, context=context)
624 if production_mode == 'consume_produce':
625 # To produce remaining qty of final product
626 vals = {'state':'confirmed'}
627 final_product_todo = [x.id for x in production.move_created_ids]
628 stock_mov_obj.write(cr, uid, final_product_todo, vals)
629 produced_products = {}
630 for produced_product in production.move_created_ids2:
631 if not produced_products.get(produced_product.product_id.id, False):
632 produced_products[produced_product.product_id.id] = 0
633 produced_products[produced_product.product_id.id] += produced_product.product_qty
635 for produce_product in production.move_created_ids:
636 produced_qty = produced_products.get(produce_product.product_id.id, 0)
637 rest_qty = production.product_qty - produced_qty
638 if rest_qty <= production_qty:
639 production_qty = rest_qty
641 stock_mov_obj.action_consume(cr, uid, [produce_product.id], production_qty, production.location_dest_id.id, context=context)
644 for raw_product in production.move_lines2:
646 parent_move_ids = [x.id for x in raw_product.move_history_ids]
647 for final_product in production.move_created_ids2:
648 if final_product.id not in parent_move_ids:
649 new_parent_ids.append(final_product.id)
650 for new_parent_id in new_parent_ids:
651 stock_mov_obj.write(cr, uid, [raw_product.id], {'move_history_ids':[(4,new_parent_id)]})
653 wf_service = netsvc.LocalService("workflow")
654 wf_service.trg_validate(uid, 'mrp.production', production_id, 'button_produce_done', cr)
657 def _costs_generate(self, cr, uid, production):
659 for wc_line in production.workcenter_lines:
660 wc = wc_line.workcenter_id
661 if wc.costs_journal_id and wc.costs_general_account_id:
662 value = wc_line.hour * wc.costs_hour
663 account = wc.costs_hour_account_id.id
664 if value and account:
666 self.pool.get('account.analytic.line').create(cr, uid, {
667 'name': wc_line.name+' (H)',
669 'account_id': account,
670 'general_account_id': wc.costs_general_account_id.id,
671 'journal_id': wc.costs_journal_id.id,
674 if wc.costs_journal_id and wc.costs_general_account_id:
675 value = wc_line.cycle * wc.costs_cycle
676 account = wc.costs_cycle_account_id.id
677 if value and account:
679 self.pool.get('account.analytic.line').create(cr, uid, {
680 'name': wc_line.name+' (C)',
682 'account_id': account,
683 'general_account_id': wc.costs_general_account_id.id,
684 'journal_id': wc.costs_journal_id.id,
689 def action_in_production(self, cr, uid, ids):
691 self.write(cr, uid, ids, {'state': 'in_production','date_start':time.strftime('%Y-%m-%d %H:%M:%S')})
694 def test_if_product(self, cr, uid, ids):
696 for production in self.browse(cr, uid, ids):
697 if not production.product_lines:
698 if not self.action_compute(cr, uid, [production.id]):
702 def _get_auto_picking(self, cr, uid, production):
705 def action_confirm(self, cr, uid, ids):
708 for production in self.browse(cr, uid, ids):
709 if not production.product_lines:
710 self.action_compute(cr, uid, [production.id])
711 production = self.browse(cr, uid, [production.id])[0]
713 pick_type = 'internal'
715 if production.bom_id.routing_id and production.bom_id.routing_id.location_id:
716 routing_loc = production.bom_id.routing_id.location_id
717 if routing_loc.usage<>'internal':
719 address_id = routing_loc.address_id and routing_loc.address_id.id or False
720 routing_loc = routing_loc.id
721 pick_name = self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.'+pick_type)
722 picking_id = self.pool.get('stock.picking').create(cr, uid, {
724 'origin': (production.origin or '').split(':')[0] +':'+production.name,
728 'address_id': address_id,
729 'auto_picking': self._get_auto_picking(cr, uid, production),
730 'company_id': production.company_id.id,
733 source = production.product_id.product_tmpl_id.property_stock_production.id
735 'name':'PROD:'+production.name,
736 'date_planned': production.date_planned,
737 'product_id': production.product_id.id,
738 'product_qty': production.product_qty,
739 'product_uom': production.product_uom.id,
740 'product_uos_qty': production.product_uos and production.product_uos_qty or False,
741 'product_uos': production.product_uos and production.product_uos.id or False,
742 'location_id': source,
743 'location_dest_id': production.location_dest_id.id,
744 'move_dest_id': production.move_prod_id.id,
746 'company_id': production.company_id.id,
748 res_final_id = self.pool.get('stock.move').create(cr, uid, data)
750 self.write(cr, uid, [production.id], {'move_created_ids': [(6, 0, [res_final_id])]})
752 for line in production.product_lines:
754 newdate = production.date_planned
755 if line.product_id.type in ('product', 'consu'):
756 res_dest_id = self.pool.get('stock.move').create(cr, uid, {
757 'name':'PROD:'+production.name,
758 'date_planned': production.date_planned,
759 'product_id': line.product_id.id,
760 'product_qty': line.product_qty,
761 'product_uom': line.product_uom.id,
762 'product_uos_qty': line.product_uos and line.product_uos_qty or False,
763 'product_uos': line.product_uos and line.product_uos.id or False,
764 'location_id': routing_loc or production.location_src_id.id,
765 'location_dest_id': source,
766 'move_dest_id': res_final_id,
768 'company_id': production.company_id.id,
770 moves.append(res_dest_id)
771 move_id = self.pool.get('stock.move').create(cr, uid, {
772 'name':'PROD:'+production.name,
773 'picking_id':picking_id,
774 'product_id': line.product_id.id,
775 'product_qty': line.product_qty,
776 'product_uom': line.product_uom.id,
777 'product_uos_qty': line.product_uos and line.product_uos_qty or False,
778 'product_uos': line.product_uos and line.product_uos.id or False,
779 'date_planned': newdate,
780 'move_dest_id': res_dest_id,
781 'location_id': production.location_src_id.id,
782 'location_dest_id': routing_loc or production.location_src_id.id,
784 'company_id': production.company_id.id,
786 proc_id = self.pool.get('mrp.procurement').create(cr, uid, {
787 'name': (production.origin or '').split(':')[0] + ':' + production.name,
788 'origin': (production.origin or '').split(':')[0] + ':' + production.name,
789 'date_planned': newdate,
790 'product_id': line.product_id.id,
791 'product_qty': line.product_qty,
792 'product_uom': line.product_uom.id,
793 'product_uos_qty': line.product_uos and line.product_qty or False,
794 'product_uos': line.product_uos and line.product_uos.id or False,
795 'location_id': production.location_src_id.id,
796 'procure_method': line.product_id.procure_method,
798 'company_id': production.company_id.id,
800 wf_service = netsvc.LocalService("workflow")
801 wf_service.trg_validate(uid, 'mrp.procurement', proc_id, 'button_confirm', cr)
802 proc_ids.append(proc_id)
803 wf_service = netsvc.LocalService("workflow")
804 wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
805 self.write(cr, uid, [production.id], {'picking_id':picking_id, 'move_lines': [(6,0,moves)], 'state':'confirmed'})
808 def force_production(self, cr, uid, ids, *args):
809 pick_obj = self.pool.get('stock.picking')
810 pick_obj.force_assign(cr, uid, [prod.picking_id.id for prod in self.browse(cr, uid, ids)])
815 class mrp_production_workcenter_line(osv.osv):
816 _name = 'mrp.production.workcenter.line'
817 _description = 'Work Orders'
820 'name': fields.char('Work Order', size=64, required=True),
821 'workcenter_id': fields.many2one('mrp.workcenter', 'Work Center', required=True),
822 'cycle': fields.float('Nbr of cycles', digits=(16,2)),
823 'hour': fields.float('Nbr of hours', digits=(16,2)),
824 'sequence': fields.integer('Sequence', required=True, help="Gives the sequence order when displaying a list of work orders."),
825 'production_id': fields.many2one('mrp.production', 'Production Order', select=True, ondelete='cascade'),
828 'sequence': lambda *a: 1,
829 'hour': lambda *a: 0,
830 'cycle': lambda *a: 0,
832 mrp_production_workcenter_line()
834 class mrp_production_product_line(osv.osv):
835 _name = 'mrp.production.product.line'
836 _description = 'Production scheduled products'
838 'name': fields.char('Name', size=64, required=True),
839 'product_id': fields.many2one('product.product', 'Product', required=True),
840 'product_qty': fields.float('Product Qty', required=True),
841 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True),
842 'product_uos_qty': fields.float('Product UOS Qty'),
843 'product_uos': fields.many2one('product.uom', 'Product UOS'),
844 'production_id': fields.many2one('mrp.production', 'Production Order', select=True),
846 mrp_production_product_line()
848 # ------------------------------------------------------------------
850 # ------------------------------------------------------------------
852 # Produce, Buy or Find products and place a move
853 # then wizard for picking lists & move
855 class mrp_procurement(osv.osv):
856 _name = "mrp.procurement"
857 _description = "Procurement"
858 _order = 'priority,date_planned'
860 'name': fields.char('Reason', size=64, required=True, help='Requisition name.'),
861 'origin': fields.char('Source Document', size=64,
862 help="Reference of the document that created this Requisition.\n"
863 "This is automatically completed by Open ERP."),
864 'priority': fields.selection([('0','Not urgent'),('1','Normal'),('2','Urgent'),('3','Very Urgent')], 'Priority', required=True),
865 'date_planned': fields.datetime('Scheduled date', required=True),
866 'date_close': fields.datetime('Date Closed'),
867 'product_id': fields.many2one('product.product', 'Product', required=True, states={'draft':[('readonly',False)]}, readonly=True),
868 'product_qty': fields.float('Quantity', required=True, states={'draft':[('readonly',False)]}, readonly=True),
869 'product_uom': fields.many2one('product.uom', 'Product UoM', required=True, states={'draft':[('readonly',False)]}, readonly=True),
870 'product_uos_qty': fields.float('UoS Quantity', states={'draft':[('readonly',False)]}, readonly=True),
871 'product_uos': fields.many2one('product.uom', 'Product UoS', states={'draft':[('readonly',False)]}, readonly=True),
872 'move_id': fields.many2one('stock.move', 'Reservation', ondelete='set null'),
874 'bom_id': fields.many2one('mrp.bom', 'BoM', ondelete='cascade', select=True),
876 'close_move': fields.boolean('Close Move at end', required=True),
877 'location_id': fields.many2one('stock.location', 'Location', required=True, states={'draft':[('readonly',False)]}, readonly=True),
878 'procure_method': fields.selection([('make_to_stock','from stock'),('make_to_order','on order')], 'Procurement Method', states={'draft':[('readonly',False)], 'confirmed':[('readonly',False)]},
879 readonly=True, required=True, help="If you encode manually a Procurement, you probably want to use" \
880 " a make to order method."),
882 'purchase_id': fields.many2one('purchase.order', 'Purchase Order'),
883 'note': fields.text('Note'),
885 'property_ids': fields.many2many('mrp.property', 'mrp_procurement_property_rel', 'procurement_id','property_id', 'Properties'),
887 'message': fields.char('Latest error', size=64, help="Exception occurred while computing procurement orders."),
888 'state': fields.selection([
890 ('confirmed','Confirmed'),
891 ('exception','Exception'),
892 ('running','Running'),
896 ('waiting','Waiting')], 'State', required=True,
897 help='When a procurement is created the state is set to \'Draft\'.\n If the procurement is confirmed, the state is set to \'Confirmed\'.\
898 \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.'),
899 'note' : fields.text('Note'),
900 'company_id': fields.many2one('res.company','Company',required=True),
903 'state': lambda *a: 'draft',
904 'priority': lambda *a: '1',
905 'date_planned': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
906 'close_move': lambda *a: 0,
907 'procure_method': lambda *a: 'make_to_order',
908 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'mrp.procurement', context=c)
911 def unlink(self, cr, uid, ids, context=None):
912 procurements = self.read(cr, uid, ids, ['state'])
914 for s in procurements:
915 if s['state'] in ['draft','cancel']:
916 unlink_ids.append(s['id'])
918 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Requisition Order(s) which are in %s State!' % s['state']))
919 return osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
921 def onchange_product_id(self, cr, uid, ids, product_id, context={}):
923 w=self.pool.get('product.product').browse(cr,uid,product_id, context)
925 'product_uom':w.uom_id.id,
926 'product_uos':w.uos_id and w.uos_id.id or w.uom_id.id
931 def check_product(self, cr, uid, ids):
932 for procurement in self.browse(cr, uid, ids):
933 if procurement.product_id.type in ('product', 'consu'):
937 def check_move_cancel(self, cr, uid, ids, context={}):
940 for procurement in self.browse(cr, uid, ids, context):
941 if procurement.move_id:
943 if not procurement.move_id.state=='cancel':
947 def check_move_done(self, cr, uid, ids, context={}):
949 for proc in self.browse(cr, uid, ids, context):
951 if not proc.move_id.state=='done':
956 # This method may be overrided by objects that override mrp.procurment
957 # for computing their own purpose
959 def _quantity_compute_get(self, cr, uid, proc, context={}):
960 if proc.product_id.type=='product':
961 if proc.move_id.product_uos:
962 return proc.move_id.product_uos_qty
965 def _uom_compute_get(self, cr, uid, proc, context={}):
966 if proc.product_id.type=='product':
967 if proc.move_id.product_uos:
968 return proc.move_id.product_uos.id
972 # Return the quantity of product shipped/produced/served, wich may be
973 # different from the planned quantity
975 def quantity_get(self, cr, uid, id, context={}):
976 proc = self.browse(cr, uid, id, context)
977 result = self._quantity_compute_get(cr, uid, proc, context)
979 result = proc.product_qty
982 def uom_get(self, cr, uid, id, context=None):
983 proc = self.browse(cr, uid, id, context)
984 result = self._uom_compute_get(cr, uid, proc, context)
986 result = proc.product_uom.id
989 def check_waiting(self, cr, uid, ids, context=[]):
990 for procurement in self.browse(cr, uid, ids, context=context):
991 if procurement.move_id and procurement.move_id.state=='auto':
995 def check_produce_service(self, cr, uid, procurement, context=[]):
998 def check_produce_product(self, cr, uid, procurement, context=[]):
999 properties = [x.id for x in procurement.property_ids]
1000 bom_id = self.pool.get('mrp.bom')._bom_find(cr, uid, procurement.product_id.id, procurement.product_uom.id, properties)
1002 cr.execute('update mrp_procurement set message=%s where id=%s', (_('No BoM defined for this product !'), procurement.id))
1006 def check_make_to_stock(self, cr, uid, ids, context={}):
1008 for procurement in self.browse(cr, uid, ids, context=context):
1009 if procurement.product_id.type=='service':
1010 ok = ok and self._check_make_to_stock_service(cr, uid, procurement, context)
1012 ok = ok and self._check_make_to_stock_product(cr, uid, procurement, context)
1015 def check_produce(self, cr, uid, ids, context={}):
1017 user = self.pool.get('res.users').browse(cr, uid, uid)
1018 for procurement in self.browse(cr, uid, ids):
1019 if procurement.product_id.product_tmpl_id.supply_method<>'produce':
1020 if procurement.product_id.seller_ids:
1021 partner = procurement.product_id.seller_ids[0].name
1022 if user.company_id and user.company_id.partner_id:
1023 if partner.id == user.company_id.partner_id.id:
1026 if procurement.product_id.product_tmpl_id.type=='service':
1027 res = res and self.check_produce_service(cr, uid, procurement, context)
1029 res = res and self.check_produce_product(cr, uid, procurement, context)
1034 def check_buy(self, cr, uid, ids):
1035 user = self.pool.get('res.users').browse(cr, uid, uid)
1036 for procurement in self.browse(cr, uid, ids):
1037 if procurement.product_id.product_tmpl_id.supply_method<>'buy':
1039 if not procurement.product_id.seller_ids:
1040 cr.execute('update mrp_procurement set message=%s where id=%s', (_('No supplier defined for this product !'), procurement.id))
1042 partner = procurement.product_id.seller_ids[0].name
1043 if user.company_id and user.company_id.partner_id:
1044 if partner.id == user.company_id.partner_id.id:
1046 address_id = self.pool.get('res.partner').address_get(cr, uid, [partner.id], ['delivery'])['delivery']
1048 cr.execute('update mrp_procurement set message=%s where id=%s', (_('No address defined for the supplier'), procurement.id))
1052 def test_cancel(self, cr, uid, ids):
1053 for record in self.browse(cr, uid, ids):
1054 if record.move_id and record.move_id.state=='cancel':
1058 def action_confirm(self, cr, uid, ids, context={}):
1059 for procurement in self.browse(cr, uid, ids):
1060 if procurement.product_qty <= 0.00:
1061 raise osv.except_osv(_('Data Insufficient !'), _('Please check the Quantity of Requisition Order(s), it should not be less than 1!'))
1062 if procurement.product_id.type in ('product', 'consu'):
1063 if not procurement.move_id:
1064 source = procurement.location_id.id
1065 if procurement.procure_method=='make_to_order':
1066 source = procurement.product_id.product_tmpl_id.property_stock_procurement.id
1067 id = self.pool.get('stock.move').create(cr, uid, {
1068 'name': 'PROC:'+procurement.name,
1069 'location_id': source,
1070 'location_dest_id': procurement.location_id.id,
1071 'product_id': procurement.product_id.id,
1072 'product_qty':procurement.product_qty,
1073 'product_uom': procurement.product_uom.id,
1074 'date_planned': procurement.date_planned,
1075 'state':'confirmed',
1076 'company_id': procurement.company_id.id,
1078 self.write(cr, uid, [procurement.id], {'move_id': id, 'close_move':1})
1081 if procurement.procure_method=='make_to_stock' and procurement.move_id.state in ('waiting',):
1082 id = self.pool.get('stock.move').write(cr, uid, [procurement.move_id.id], {'state':'confirmed'})
1083 self.write(cr, uid, ids, {'state':'confirmed','message':''})
1086 def action_move_assigned(self, cr, uid, ids, context={}):
1087 self.write(cr, uid, ids, {'state':'running','message':_('from stock: products assigned.')})
1090 def _check_make_to_stock_service(self, cr, uid, procurement, context={}):
1093 def _check_make_to_stock_product(self, cr, uid, procurement, context={}):
1095 if procurement.move_id:
1096 id = procurement.move_id.id
1097 if not (procurement.move_id.state in ('done','assigned','cancel')):
1098 ok = ok and self.pool.get('stock.move').action_assign(cr, uid, [id])
1099 cr.execute('select count(id) from stock_warehouse_orderpoint where product_id=%s', (procurement.product_id.id,))
1100 if not cr.fetchone()[0]:
1101 cr.execute('update mrp_procurement set message=%s where id=%s', (_('from stock and no minimum orderpoint rule defined'), procurement.id))
1104 def action_produce_assign_service(self, cr, uid, ids, context={}):
1105 for procurement in self.browse(cr, uid, ids):
1106 self.write(cr, uid, [procurement.id], {'state':'running'})
1109 def action_produce_assign_product(self, cr, uid, ids, context={}):
1111 @summary : This is action which call from workflow to assign production order to procuments
1114 res = self.make_mo(cr, uid, ids, context=context)
1115 return 1 #TO CHECK: why workflow is generated error if return True
1117 def make_mo(self, cr, uid, ids, context={}):
1119 @summary : Make Manufecturing(production) order from procurement
1121 @return : New created Production Orders procurement wise
1124 company = self.pool.get('res.users').browse(cr, uid, uid, context).company_id
1125 for procurement in self.browse(cr, uid, ids):
1126 res_id = procurement.move_id.id
1127 loc_id = procurement.location_id.id
1128 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)
1129 newdate = newdate - DateTime.RelativeDateTime(days=company.manufacturing_lead)
1130 produce_id = self.pool.get('mrp.production').create(cr, uid, {
1131 'origin': procurement.origin,
1132 'product_id': procurement.product_id.id,
1133 'product_qty': procurement.product_qty,
1134 'product_uom': procurement.product_uom.id,
1135 'product_uos_qty': procurement.product_uos and procurement.product_uos_qty or False,
1136 'product_uos': procurement.product_uos and procurement.product_uos.id or False,
1137 'location_src_id': procurement.location_id.id,
1138 'location_dest_id': procurement.location_id.id,
1139 'bom_id': procurement.bom_id and procurement.bom_id.id or False,
1140 'date_planned': newdate.strftime('%Y-%m-%d %H:%M:%S'),
1141 'move_prod_id': res_id,
1142 'company_id': procurement.company_id.id,
1144 res[procurement.id] = produce_id
1145 self.write(cr, uid, [procurement.id], {'state':'running'})
1146 bom_result = self.pool.get('mrp.production').action_compute(cr, uid,
1147 [produce_id], properties=[x.id for x in procurement.property_ids])
1148 wf_service = netsvc.LocalService("workflow")
1149 wf_service.trg_validate(uid, 'mrp.production', produce_id, 'button_confirm', cr)
1150 self.pool.get('stock.move').write(cr, uid, [res_id],
1151 {'location_id':procurement.location_id.id})
1154 def action_po_assign(self, cr, uid, ids, context={}):
1156 @summary : This is action which call from workflow to assign purchase order to procuments
1159 res = self.make_po(cr, uid, ids, context=context)
1160 return 1 #TO CHECK: why workflow is generated error if return True
1162 def make_po(self, cr, uid, ids, context={}):
1164 @summary : Make purchase order from procurement
1166 @return : New created Purchase Orders procurement wise
1169 company = self.pool.get('res.users').browse(cr, uid, uid, context).company_id
1170 for procurement in self.browse(cr, uid, ids):
1171 res_id = procurement.move_id.id
1172 partner = procurement.product_id.seller_ids[0].name
1173 partner_id = partner.id
1174 address_id = self.pool.get('res.partner').address_get(cr, uid, [partner_id], ['delivery'])['delivery']
1175 pricelist_id = partner.property_product_pricelist_purchase.id
1177 uom_id = procurement.product_id.uom_po_id.id
1179 qty = self.pool.get('product.uom')._compute_qty(cr, uid, procurement.product_uom.id, procurement.product_qty, uom_id)
1180 if procurement.product_id.seller_ids[0].qty:
1181 qty=max(qty,procurement.product_id.seller_ids[0].qty)
1183 price = self.pool.get('product.pricelist').price_get(cr, uid, [pricelist_id], procurement.product_id.id, qty, False, {'uom': uom_id})[pricelist_id]
1185 newdate = DateTime.strptime(procurement.date_planned, '%Y-%m-%d %H:%M:%S')
1186 newdate = newdate - DateTime.RelativeDateTime(days=company.po_lead)
1187 newdate = newdate - procurement.product_id.seller_ids[0].delay
1189 #Passing partner_id to context for purchase order line integrity of Line name
1190 context.update({'lang':partner.lang, 'partner_id':partner_id})
1192 product=self.pool.get('product.product').browse(cr,uid,procurement.product_id.id,context=context)
1195 'name': product.partner_ref,
1197 'product_id': procurement.product_id.id,
1198 'product_uom': uom_id,
1199 'price_unit': price,
1200 'date_planned': newdate.strftime('%Y-%m-%d %H:%M:%S'),
1201 'move_dest_id': res_id,
1202 'notes':product.description_purchase,
1205 taxes_ids = procurement.product_id.product_tmpl_id.supplier_taxes_id
1206 taxes = self.pool.get('account.fiscal.position').map_tax(cr, uid, partner.property_account_position, taxes_ids)
1208 'taxes_id':[(6,0,taxes)]
1210 purchase_id = self.pool.get('purchase.order').create(cr, uid, {
1211 'origin': procurement.origin,
1212 'partner_id': partner_id,
1213 'partner_address_id': address_id,
1214 'location_id': procurement.location_id.id,
1215 'pricelist_id': pricelist_id,
1216 'order_line': [(0,0,line)],
1217 'company_id': procurement.company_id.id,
1218 'fiscal_position': partner.property_account_position and partner.property_account_position.id or False
1220 res[procurement.id] = purchase_id
1221 self.write(cr, uid, [procurement.id], {'state':'running', 'purchase_id':purchase_id})
1224 def action_cancel(self, cr, uid, ids):
1227 for proc in self.browse(cr, uid, ids):
1229 if proc.move_id.state not in ('done','cancel'):
1230 todo2.append(proc.move_id.id)
1232 if proc.move_id and proc.move_id.state=='waiting':
1233 todo.append(proc.move_id.id)
1235 self.pool.get('stock.move').action_cancel(cr, uid, todo2)
1237 self.pool.get('stock.move').write(cr, uid, todo, {'state':'assigned'})
1238 self.write(cr, uid, ids, {'state':'cancel'})
1239 wf_service = netsvc.LocalService("workflow")
1241 wf_service.trg_trigger(uid, 'mrp.procurement', id, cr)
1244 def action_check_finnished(self, cr, uid, ids):
1245 return self.check_move_done(cr, uid, ids)
1247 def action_check(self, cr, uid, ids):
1249 for procurement in self.browse(cr, uid, ids):
1250 if procurement.move_id.state=='assigned' or procurement.move_id.state=='done':
1251 self.action_done(cr, uid, [procurement.id])
1255 def action_ready(self, cr, uid, ids):
1256 res = self.write(cr, uid, ids, {'state':'ready'})
1259 def action_done(self, cr, uid, ids):
1260 for procurement in self.browse(cr, uid, ids):
1261 if procurement.move_id:
1262 if procurement.close_move and (procurement.move_id.state <> 'done'):
1263 self.pool.get('stock.move').action_done(cr, uid, [procurement.move_id.id])
1264 res = self.write(cr, uid, ids, {'state':'done', 'date_close':time.strftime('%Y-%m-%d')})
1265 wf_service = netsvc.LocalService("workflow")
1267 wf_service.trg_trigger(uid, 'mrp.procurement', id, cr)
1270 def run_scheduler(self, cr, uid, automatic=False, use_new_cursor=False, context=None):
1272 use_new_cursor: False or the dbname
1276 self._procure_confirm(cr, uid, use_new_cursor=use_new_cursor, context=context)
1277 self._procure_orderpoint_confirm(cr, uid, automatic=automatic,\
1278 use_new_cursor=use_new_cursor, context=context)
1280 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: