[REF] purchase: search view of purchase order and form view of merge order wizard
[odoo/odoo.git] / addons / mrp / mrp.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
6 #
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.
11 #
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.
16 #
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/>.
19 #
20 ##############################################################################
21
22 from mx import DateTime
23 from osv import fields
24 from osv import osv
25 from tools.translate import _
26 import ir
27 import netsvc
28 import time
29
30 #----------------------------------------------------------
31 # Work Centers
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
36 #
37 # TODO: Work Center may be recursive ?
38 #
39 class mrp_workcenter(osv.osv):
40     _name = 'mrp.workcenter'
41     _description = 'Work Center'
42     _inherits = {'resource.resource':"resource_id"}
43     _columns = {
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'),
60     }
61     _defaults = {
62         'capacity_per_cycle': lambda *a: 1.0,
63      }
64 mrp_workcenter()
65
66
67 class mrp_property_group(osv.osv):
68     _name = 'mrp.property.group'
69     _description = 'Property Group'
70     _columns = {
71         'name': fields.char('Property Group', size=64, required=True),
72         'description': fields.text('Description'),
73     }
74 mrp_property_group()
75
76 class mrp_property(osv.osv):
77     _name = 'mrp.property'
78     _description = 'Property'
79     _columns = {
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'),
84     }
85     _defaults = {
86         'composition': lambda *a: 'min',
87     }
88 mrp_property()
89
90 class mrp_routing(osv.osv):
91     _name = 'mrp.routing'
92     _description = 'Routing'
93     _columns = {
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),
97
98         'note': fields.text('Description'),
99         'workcenter_lines': fields.one2many('mrp.routing.workcenter', 'routing_id', 'Work Centers'),
100
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."
105         ),
106     }
107     _defaults = {
108         'active': lambda *a: 1,
109     }
110 mrp_routing()
111
112 class mrp_routing_workcenter(osv.osv):
113     _name = 'mrp.routing.workcenter'
114     _description = 'Routing workcenter usage'
115     _columns = {
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')
126     }
127     _defaults = {
128         'cycle_nbr': lambda *a: 1.0,
129         'hour_nbr': lambda *a: 0.0,
130     }
131 mrp_routing_workcenter()
132
133 class mrp_bom(osv.osv):
134     _name = 'mrp.bom'
135     _description = 'Bills of Material'
136     def _child_compute(self, cr, uid, ids, name, arg, context={}):
137         result = {}
138         for bom in self.browse(cr, uid, ids, context=context):
139             result[bom.id] = map(lambda x: x.id, bom.bom_lines)
140             if bom.bom_lines:
141                 continue
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)])
145                 if sids:
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)
148         return result
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:
153                 res[line.id] = 'set'
154                 continue
155             if line.bom_lines or line.type=='phantom':
156                 continue
157             if line.product_id.supply_method=='produce':
158                 if line.product_id.procure_method=='make_to_stock':
159                     res[line.id] = 'stock'
160                 else:
161                     res[line.id] = 'order'
162         return res
163     _columns = {
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'),
193
194     }
195     _defaults = {
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, 
203     }
204     _order = "sequence"
205     _sql_constraints = [
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 !'),
208     ]
209
210     def _check_recursion(self, cr, uid, ids):
211         level = 100
212         while len(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()))
215             if not level:
216                 return False
217             level -= 1
218         return True
219     _constraints = [
220         (_check_recursion, 'Error ! You can not create recursive BoM.', ['parent_id'])
221     ]
222
223
224     def onchange_product_id(self, cr, uid, ids, product_id, name, context={}):
225         if product_id:
226             prod=self.pool.get('product.product').browse(cr,uid,[product_id])[0]
227             v = {'product_uom':prod.uom_id.id}
228             if not name:
229                 v['name'] = prod.name
230             return {'value': v}
231         return {}
232
233     def _bom_find(self, cr, uid, product_id, product_uom, properties=[]):
234         bom_result = False
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())
238         max_prop = 0
239         result = False
240         for bom in self.pool.get('mrp.bom').browse(cr, uid, ids):
241             prop = 0
242             for prop_id in bom.property_ids:
243                 if prop_id.id in properties:
244                     prop+=1
245             if (prop>max_prop) or ((max_prop==0) and not result):
246                 result = bom.id
247                 max_prop = prop
248         return result
249
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
255         result = []
256         result2 = []
257         phantom=False
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)
260             if newbom:
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]
264                 phantom=True
265             else:
266                 phantom=False
267         if not phantom:
268             if addthis and not bom.bom_lines:
269                 result.append(
270                 {
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,
277                 })
278             if bom.routing_id:
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
284                     result2.append({
285                         'name': bom.routing_id.name,
286                         'workcenter_id': wc.id,
287                         'sequence': level+(wc_use.sequence or 0),
288                         'cycle': cycle,
289                         'hour': float(wc_use.hour_nbr*mult + (wc.time_start+wc.time_stop+cycle*wc.time_cycle) * (wc.time_efficiency or 1.0)),
290                     })
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
296
297     def set_indices(self, cr, uid, ids, context = {}):
298         if not ids or (ids and not ids[0]):
299             return True
300         res = self.read(cr, uid, ids, ['revision_ids', 'revision_type'])
301         rev_ids = res[0]['revision_ids']
302         idx = 1
303         new_idx = []
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})
308             else:
309                 bom_rev_obj.write(cr, uid, [rev_id], {'indice' : "%c"%(idx+96,)})
310             idx+=1
311         return True
312
313 mrp_bom()
314
315 class mrp_bom_revision(osv.osv):
316     _name = 'mrp.bom.revision'
317     _description = 'Bill of material revisions'
318     _columns = {
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),
326     }
327
328     _defaults = {
329         'author_id': lambda x,y,z,c: z,
330         'date': lambda *a: time.strftime('%Y-%m-%d'),
331     }
332
333 mrp_bom_revision()
334
335 def rounding(f, r):
336     if not r:
337         return f
338     return round(f / r) * r
339
340 class many2many_domain(fields.many2many):
341     def set(self, cr, obj, id, name, values, user=None, context=None):
342         if not values:
343             return
344         return super(many2many_domain, self).set(cr, obj, id, name, values, user=user,
345                 context=context)
346
347     def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
348         if not context:
349             context = {}
350         res = {}
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))])
357             res[prod.id] = final
358         return res
359
360 class one2many_domain(fields.one2many):
361     def set(self, cr, obj, id, field, values, user=None, context=None):
362         if not values:
363             return
364         return super(one2many_domain, self).set(cr, obj, id, field, values, 
365                                             user=user, context=context)
366
367     def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
368         if not context:
369             context = {}
370         res = {}
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))])
376             res[prod.id] = final
377         return res
378
379 class mrp_production(osv.osv):
380     _name = 'mrp.production'
381     _description = 'Production'
382     _date_name  = 'date_planned'    
383
384     def _production_calc(self, cr, uid, ids, prop, unknow_none, context={}):
385         result = {}
386         for prod in self.browse(cr, uid, ids, context=context):
387             result[prod.id] = {
388                 'hour_total': 0.0,
389                 'cycle_total': 0.0,
390             }
391             for wc in prod.workcenter_lines:
392                 result[prod.id]['hour_total'] += wc.hour
393                 result[prod.id]['cycle_total'] += wc.cycle
394         return result
395
396     def _production_date_end(self, cr, uid, ids, prop, unknow_none, context={}):
397         result = {}
398         for prod in self.browse(cr, uid, ids, context=context):
399             result[prod.id] = prod.date_planned
400         return result
401
402     def _production_date(self, cr, uid, ids, prop, unknow_none, context={}):
403         result = {}
404         for prod in self.browse(cr, uid, ids, context=context):
405             result[prod.id] = prod.date_planned[:10]
406         return result
407
408     def _ref_calc(self, cr, uid, ids, field_names=None, arg=False, context={}):
409         res = {}
410         for f in field_names:
411             for order_id in ids:
412                 res[order_id] = {f:False}
413         return res
414
415     _columns = {
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'),
419
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),
425
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."),
430
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'),
436
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."),
439
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'),
454
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),
458     }
459     _defaults = {
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),
466     }
467     _order = 'date_planned asc, priority desc';
468     def unlink(self, cr, uid, ids, context=None):
469         productions = self.read(cr, uid, ids, ['state'])
470         unlink_ids = []
471         for s in productions:
472             if s['state'] in ['draft','cancel']:
473                 unlink_ids.append(s['id'])
474             else:
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)
477
478     def copy(self, cr, uid, id, default=None,context=None):
479         if not default:
480             default = {}
481         default.update({
482             'name': self.pool.get('ir.sequence').get(cr, uid, 'mrp.production'),
483             'move_lines' : [],
484             'move_created_ids': [],
485             'state': 'draft'
486         })
487         return super(mrp_production, self).copy(cr, uid, id, default, context)
488
489     def location_id_change(self, cr, uid, ids, src, dest, context={}):
490         if dest:
491             return {}
492         if src:
493             return {'value': {'location_dest_id': src}}
494         return {}
495
496     def product_id_change(self, cr, uid, ids, product):
497         if not product:
498             return {}
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}
503
504     def bom_id_change(self, cr, uid, ids, product):
505         if not product:
506             return {}
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}
511
512     def action_picking_except(self, cr, uid, ids):
513         self.write(cr, uid, ids, {'state':'picking_except'})
514         return True
515
516     def action_compute(self, cr, uid, ids, properties=[]):
517         results = []
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
526             if not bom_point:
527                 bom_id = bom_obj._bom_find(cr, uid, production.product_id.id, production.product_uom.id, properties)
528                 if bom_id:
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})
532
533             if not bom_id:
534                 raise osv.except_osv(_('Error'), _("Couldn't find bill of material for product"))
535
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})
538
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)
541             results = res[0]
542             results2 = res[1]
543             for line in results:
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)
549         return len(results)
550
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,[])]})
558         return True
559
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})
569         return True
570
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')})
575
576     def test_production_done(self, cr, uid, ids):
577         res = True
578         for production in self.browse(cr, uid, ids):            
579             if production.move_lines:                
580                res = False
581
582             if production.move_created_ids:                
583                res = False        
584         return res
585
586     def action_produce(self, cr, uid, production_id, production_qty, production_mode, context=None):
587         """ 
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.
592         
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).
599
600              @return:  True
601         
602         """              
603         stock_mov_obj = self.pool.get('stock.move')
604         production = self.browse(cr, uid, production_id)
605         
606         raw_product_todo = []
607         final_product_todo = []        
608         
609         if production_mode in ['consume','consume_produce']:
610             # To consume remaining qty of raw materials 
611             consumed_products = {}
612             produced_qty = 0
613             for consumed_product in production.move_lines2:
614                 if consumed_product.scraped:
615                     continue
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
619             
620             for produced_product in production.move_created_ids2:
621                 if produced_product.scraped:
622                     continue
623                 produced_qty += produced_product.product_qty
624
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            
631                 if rest_qty > 0:
632                     stock_mov_obj.action_consume(cr, uid, [raw_product.id], rest_qty, production.location_src_id.id, context=context)
633
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:
642                     continue
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
646
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 
652                 if rest_qty > 0 :
653                     stock_mov_obj.action_consume(cr, uid, [produce_product.id], production_qty, production.location_dest_id.id, context=context)            
654         
655         
656         for raw_product in production.move_lines2: 
657             new_parent_ids = []           
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)]})
664
665         wf_service = netsvc.LocalService("workflow")
666         wf_service.trg_validate(uid, 'mrp.production', production_id, 'button_produce_done', cr)
667         return True
668
669     def _costs_generate(self, cr, uid, production):
670         amount = 0.0
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:
678                     amount += value
679                     analytic_line_obj.create(cr, uid, {
680                         'name': wc_line.name+' (H)',
681                         'amount': value,
682                         'account_id': account,
683                         'general_account_id': wc.costs_general_account_id.id,
684                         'journal_id': wc.costs_journal_id.id,
685                         'code': wc.code
686                     } )
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:
691                     amount += value
692                     analytic_line_obj.create(cr, uid, {
693                         'name': wc_line.name+' (C)',
694                         'amount': value,
695                         'account_id': account,
696                         'general_account_id': wc.costs_general_account_id.id,
697                         'journal_id': wc.costs_journal_id.id,
698                         'code': wc.code
699                     } )
700         return amount
701
702     def action_in_production(self, cr, uid, ids):
703         move_ids = []        
704         self.write(cr, uid, ids, {'state': 'in_production','date_start':time.strftime('%Y-%m-%d %H:%M:%S')})
705         return True
706
707     def test_if_product(self, cr, uid, ids):
708         res = True
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]):
712                     res = False
713         return res
714
715     def _get_auto_picking(self, cr, uid, production):
716         return True
717
718     def action_confirm(self, cr, uid, ids):
719         picking_id=False
720         proc_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]
729             routing_loc = None
730             pick_type = 'internal'
731             address_id = False
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':
735                     pick_type = 'out'
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, {
740                 'name': pick_name,
741                 'origin': (production.origin or '').split(':')[0] +':'+production.name,
742                 'type': pick_type,
743                 'move_type': 'one',
744                 'state': 'auto',
745                 'address_id': address_id,
746                 'auto_picking': self._get_auto_picking(cr, uid, production),
747                 'company_id': production.company_id.id,
748             })
749
750             source = production.product_id.product_tmpl_id.property_stock_production.id
751             data = {
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,
762                 'state': 'waiting',
763                 'company_id': production.company_id.id,
764             }
765             res_final_id = move_obj.create(cr, uid, data)
766
767             self.write(cr, uid, [production.id], {'move_created_ids': [(6, 0, [res_final_id])]})
768             moves = []
769             for line in production.product_lines:
770                 move_id=False
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,
784                         'state': 'waiting',
785                         'company_id': production.company_id.id,
786                     })
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,
800                         'state': 'waiting',
801                         'company_id': production.company_id.id,
802                     })
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,
814                     'move_id': move_id,
815                     'company_id': production.company_id.id,
816                 })
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'})
823         return picking_id
824
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)])
828         return True
829
830 mrp_production()
831
832 class mrp_production_workcenter_line(osv.osv):
833     _name = 'mrp.production.workcenter.line'
834     _description = 'Work Orders'
835     _order = 'sequence'
836     _columns = {
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'),
843     }
844     _defaults = {
845         'sequence': lambda *a: 1,
846         'hour': lambda *a: 0,
847         'cycle': lambda *a: 0,
848     }
849 mrp_production_workcenter_line()
850
851 class mrp_production_product_line(osv.osv):
852     _name = 'mrp.production.product.line'
853     _description = 'Production scheduled products'
854     _columns = {
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),
862     }
863 mrp_production_product_line()
864
865 # ------------------------------------------------------------------
866 # Procurement
867 # ------------------------------------------------------------------
868 #
869 # Produce, Buy or Find products and place a move
870 #     then wizard for picking lists & move
871 #
872 class mrp_procurement(osv.osv):
873     _name = "mrp.procurement"
874     _description = "Procurement"
875     _order = 'priority,date_planned'
876     _columns = {
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'),
890
891         'bom_id': fields.many2one('mrp.bom', 'BoM', ondelete='cascade', select=True),
892
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."),
898
899         'purchase_id': fields.many2one('purchase.order', 'Purchase Order'),
900         'note': fields.text('Note'),
901
902         'property_ids': fields.many2many('mrp.property', 'mrp_procurement_property_rel', 'procurement_id','property_id', 'Properties'),
903
904         'message': fields.char('Latest error', size=64, help="Exception occurred while computing procurement orders."),
905         'state': fields.selection([
906             ('draft','Draft'),
907             ('confirmed','Confirmed'),
908             ('exception','Exception'),
909             ('running','Running'),
910             ('cancel','Cancel'),
911             ('ready','Ready'),
912             ('done','Done'),
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),
918     }
919     _defaults = {
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)
926     }
927
928     def unlink(self, cr, uid, ids, context=None):
929         procurements = self.read(cr, uid, ids, ['state'])
930         unlink_ids = []
931         for s in procurements:
932             if s['state'] in ['draft','cancel']:
933                 unlink_ids.append(s['id'])
934             else:
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)
937
938     def onchange_product_id(self, cr, uid, ids, product_id, context={}):
939         if product_id:
940             w=self.pool.get('product.product').browse(cr,uid,product_id, context)
941             v = {
942                 'product_uom':w.uom_id.id,
943                 'product_uos':w.uos_id and w.uos_id.id or w.uom_id.id
944             }
945             return {'value': v}
946         return {}
947
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'):
951                 return True
952         return False
953
954     def check_move_cancel(self, cr, uid, ids, context={}):
955         res = True
956         ok = False
957         for procurement in self.browse(cr, uid, ids, context):
958             if procurement.move_id:
959                 ok = True
960                 if not procurement.move_id.state=='cancel':
961                     res = False
962         return res and ok
963
964     def check_move_done(self, cr, uid, ids, context={}):
965         res = True
966         for proc in self.browse(cr, uid, ids, context):
967             if proc.move_id:
968                 if not proc.move_id.state=='done':
969                     res = False
970         return res
971
972     #
973     # This method may be overrided by objects that override mrp.procurment
974     # for computing their own purpose
975     #
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
980         return False
981
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
986         return False
987
988     #
989     # Return the quantity of product shipped/produced/served, wich may be
990     # different from the planned quantity
991     #
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)
995         if not result:
996             result = proc.product_qty
997         return result
998
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)
1002         if not result:
1003             result = proc.product_uom.id
1004         return result
1005
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':
1009                 return True
1010         return False
1011
1012     def check_produce_service(self, cr, uid, procurement, context=[]):
1013         return True
1014
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)
1018         if not bom_id:
1019             cr.execute('update mrp_procurement set message=%s where id=%s', (_('No BoM defined for this product !'), procurement.id))
1020             return False
1021         return True
1022
1023     def check_make_to_stock(self, cr, uid, ids, context={}):
1024         ok = True
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)
1028             else:
1029                 ok = ok and self._check_make_to_stock_product(cr, uid, procurement, context)
1030         return ok
1031
1032     def check_produce(self, cr, uid, ids, context={}):
1033         res = True
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:
1041                             return True
1042                 return False
1043             if procurement.product_id.product_tmpl_id.type=='service':
1044                 res = res and self.check_produce_service(cr, uid, procurement, context)
1045             else:
1046                 res = res and self.check_produce_product(cr, uid, procurement, context)
1047             if not res:
1048                 return False
1049         return res
1050
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':
1056                 return False
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))
1059                 return False
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:
1063                     return False
1064             address_id = partner_obj.address_get(cr, uid, [partner.id], ['delivery'])['delivery']
1065             if not address_id:
1066                 cr.execute('update mrp_procurement set message=%s where id=%s', (_('No address defined for the supplier'), procurement.id))
1067                 return False
1068         return True
1069
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':
1073                 return True
1074         return False
1075
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,
1096                     })
1097                     self.write(cr, uid, [procurement.id], {'move_id': id, 'close_move':1})
1098                 else:
1099                     # TODO: check this
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':''})
1103         return True
1104
1105     def action_move_assigned(self, cr, uid, ids, context={}):
1106         self.write(cr, uid, ids, {'state':'running','message':_('from stock: products assigned.')})
1107         return True
1108
1109     def _check_make_to_stock_service(self, cr, uid, procurement, context={}):
1110         return True
1111
1112     def _check_make_to_stock_product(self, cr, uid, procurement, context={}):
1113         ok = True
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))
1121         return ok
1122
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'})
1126         return True
1127
1128     def action_produce_assign_product(self, cr, uid, ids, context={}):
1129         """
1130         This is action which call from workflow to assign production order to procuments
1131         @return  : True
1132         """
1133         res = self.make_mo(cr, uid, ids, context=context)
1134         res = res.values()
1135         return len(res) and res[0] or 0 #TO CHECK: why workflow is generated error if return not integer value
1136
1137     def make_mo(self, cr, uid, ids, context={}):
1138         """
1139         Make Manufecturing(production) order from procurement
1140         
1141         @return : New created Production Orders procurement wise 
1142         """
1143         res = {}
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,
1166             })
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})
1174         return res
1175     
1176     def action_po_assign(self, cr, uid, ids, context={}):
1177         """
1178         This is action which call from workflow to assign purchase order to procuments
1179         @return  : True
1180         """
1181         res = self.make_po(cr, uid, ids, context=context)
1182         res = res.values()
1183         return len(res) and res[0] or 0 #TO CHECK: why workflow is generated error if return not integer value
1184
1185     def make_po(self, cr, uid, ids, context={}):
1186         """
1187         Make purchase order from procurement
1188         
1189         @return : New created Purchase Orders procurement wise
1190         """
1191         res = {}
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
1205
1206             uom_id = procurement.product_id.uom_po_id.id
1207
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)
1211
1212             price = pricelist_obj.price_get(cr, uid, [pricelist_id], procurement.product_id.id, qty, False, {'uom': uom_id})[pricelist_id]
1213
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
1217
1218             #Passing partner_id to context for purchase order line integrity of Line name
1219             context.update({'lang':partner.lang, 'partner_id':partner_id})
1220
1221             product = prod_obj.browse(cr,uid,procurement.product_id.id,context=context)
1222
1223             line = {
1224                 'name': product.partner_ref,
1225                 'product_qty': qty,
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,
1232             }
1233
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)
1236             line.update({
1237                 'taxes_id':[(6,0,taxes)]
1238             })
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
1248             })
1249             res[procurement.id] = purchase_id
1250             self.write(cr, uid, [procurement.id], {'state':'running', 'purchase_id':purchase_id})
1251         return res
1252
1253     def action_cancel(self, cr, uid, ids):
1254         todo = []
1255         todo2 = []
1256         move_obj = self.pool.get('stock.move')
1257         for proc in self.browse(cr, uid, ids):
1258             if proc.close_move:
1259                 if proc.move_id.state not in ('done','cancel'):
1260                     todo2.append(proc.move_id.id)
1261             else:
1262                 if proc.move_id and proc.move_id.state=='waiting':
1263                     todo.append(proc.move_id.id)
1264         if len(todo2):
1265             move_obj.action_cancel(cr, uid, todo2)
1266         if len(todo):
1267             move_obj.write(cr, uid, todo, {'state':'assigned'})
1268         self.write(cr, uid, ids, {'state':'cancel'})
1269         wf_service = netsvc.LocalService("workflow")
1270         for id in ids:
1271             wf_service.trg_trigger(uid, 'mrp.procurement', id, cr)
1272         return True
1273
1274     def action_check_finnished(self, cr, uid, ids):
1275         return self.check_move_done(cr, uid, ids)
1276
1277     def action_check(self, cr, uid, ids):
1278         ok = False
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])
1282                 ok = True
1283         return ok
1284
1285     def action_ready(self, cr, uid, ids):
1286         res = self.write(cr, uid, ids, {'state':'ready'})
1287         return res
1288
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")
1297         for id in ids:
1298             wf_service.trg_trigger(uid, 'mrp.procurement', id, cr)
1299         return res
1300
1301     def run_scheduler(self, cr, uid, automatic=False, use_new_cursor=False, context=None):
1302         '''
1303         use_new_cursor: False or the dbname
1304         '''
1305         if not context:
1306             context={}
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)
1310 mrp_procurement()
1311 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
1312