073e46b3380b9d389d7c2ff64f80b34f71b86e44
[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         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})
307             else:
308                 self.pool.get('mrp.bom.revision').write(cr, uid, [rev_id], {'indice' : "%c"%(idx+96,)})
309             idx+=1
310         return True
311
312 mrp_bom()
313
314 class mrp_bom_revision(osv.osv):
315     _name = 'mrp.bom.revision'
316     _description = 'Bill of material revisions'
317     _columns = {
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),
325     }
326
327     _defaults = {
328         'author_id': lambda x,y,z,c: z,
329         'date': lambda *a: time.strftime('%Y-%m-%d'),
330     }
331
332 mrp_bom_revision()
333
334 def rounding(f, r):
335     if not r:
336         return f
337     return round(f / r) * r
338
339 class many2many_domain(fields.many2many):
340     def set(self, cr, obj, id, name, values, user=None, context=None):
341         if not values:
342             return
343         return super(many2many_domain, self).set(cr, obj, id, name, values, user=user,
344                 context=context)
345
346     def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
347         if not context:
348             context = {}
349         res = {}
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))])
356             res[prod.id] = final
357         return res
358
359 class one2many_domain(fields.one2many):
360     def set(self, cr, obj, id, field, values, user=None, context=None):
361         if not values:
362             return
363         return super(one2many_domain, self).set(cr, obj, id, field, values, 
364                                             user=user, context=context)
365
366     def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
367         if not context:
368             context = {}
369         res = {}
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))])
375             res[prod.id] = final
376         return res
377
378 class mrp_production(osv.osv):
379     _name = 'mrp.production'
380     _description = 'Production'
381     _date_name  = 'date_planned'    
382
383     def _production_calc(self, cr, uid, ids, prop, unknow_none, context={}):
384         result = {}
385         for prod in self.browse(cr, uid, ids, context=context):
386             result[prod.id] = {
387                 'hour_total': 0.0,
388                 'cycle_total': 0.0,
389             }
390             for wc in prod.workcenter_lines:
391                 result[prod.id]['hour_total'] += wc.hour
392                 result[prod.id]['cycle_total'] += wc.cycle
393         return result
394
395     def _production_date_end(self, cr, uid, ids, prop, unknow_none, context={}):
396         result = {}
397         for prod in self.browse(cr, uid, ids, context=context):
398             result[prod.id] = prod.date_planned
399         return result
400
401     def _production_date(self, cr, uid, ids, prop, unknow_none, context={}):
402         result = {}
403         for prod in self.browse(cr, uid, ids, context=context):
404             result[prod.id] = prod.date_planned[:10]
405         return result
406
407     def _ref_calc(self, cr, uid, ids, field_names=None, arg=False, context={}):
408         res = {}
409         for f in field_names:
410             for order_id in ids:
411                 res[order_id] = {f:False}
412         return res
413
414     _columns = {
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'),
418
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),
424
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."),
429
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'),
435
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."),
438
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'),
453
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),
457     }
458     _defaults = {
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),
465     }
466     _order = 'date_planned asc, priority desc';
467     def unlink(self, cr, uid, ids, context=None):
468         productions = self.read(cr, uid, ids, ['state'])
469         unlink_ids = []
470         for s in productions:
471             if s['state'] in ['draft','cancel']:
472                 unlink_ids.append(s['id'])
473             else:
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)
476
477     def copy(self, cr, uid, id, default=None,context=None):
478         if not default:
479             default = {}
480         default.update({
481             'name': self.pool.get('ir.sequence').get(cr, uid, 'mrp.production'),
482             'move_lines' : [],
483             'move_created_ids': [],
484             'state': 'draft'
485         })
486         return super(mrp_production, self).copy(cr, uid, id, default, context)
487
488     def location_id_change(self, cr, uid, ids, src, dest, context={}):
489         if dest:
490             return {}
491         if src:
492             return {'value': {'location_dest_id': src}}
493         return {}
494
495     def product_id_change(self, cr, uid, ids, product):
496         if not product:
497             return {}
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}
502
503     def bom_id_change(self, cr, uid, ids, product):
504         if not product:
505             return {}
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}
510
511     def action_picking_except(self, cr, uid, ids):
512         self.write(cr, uid, ids, {'state':'picking_except'})
513         return True
514
515     def action_compute(self, cr, uid, ids, properties=[]):
516         results = []
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
522             if not bom_point:
523                 bom_id = self.pool.get('mrp.bom')._bom_find(cr, uid, production.product_id.id, production.product_uom.id, properties)
524                 if bom_id:
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})
528
529             if not bom_id:
530                 raise osv.except_osv(_('Error'), _("Couldn't find bill of material for product"))
531
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})
534
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)
537             results = res[0]
538             results2 = res[1]
539             for line in results:
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)
545         return len(results)
546
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,[])]})
553         return True
554
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})
563         return True
564
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')})
569
570     def test_production_done(self, cr, uid, ids):
571         res = True
572         for production in self.browse(cr, uid, ids):            
573             if production.move_lines:                
574                res = False
575
576             if production.move_created_ids:                
577                res = False        
578         return res
579
580     def action_produce(self, cr, uid, production_id, production_qty, production_mode, context=None):
581         """ 
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.
586         
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).
593
594              @return:  True
595         
596         """              
597         stock_mov_obj = self.pool.get('stock.move')
598         production = self.browse(cr, uid, production_id)
599         
600         raw_product_todo = []
601         final_product_todo = []        
602         
603         if production_mode in ['consume','consume_produce']:
604             # To consume remaining qty of raw materials 
605             consumed_products = {}
606             produced_qty = 0
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
611             
612             for produced_product in production.move_created_ids2:
613                 produced_qty += produced_product.product_qty
614
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            
621                 if rest_qty > 0:
622                     stock_mov_obj.action_consume(cr, uid, [raw_product.id], rest_qty, production.location_src_id.id, context=context)
623
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
634
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 
640                 if rest_qty > 0 :
641                     stock_mov_obj.action_consume(cr, uid, [produce_product.id], production_qty, production.location_dest_id.id, context=context)            
642         
643         
644         for raw_product in production.move_lines2: 
645             new_parent_ids = []           
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)]})
652
653         wf_service = netsvc.LocalService("workflow")
654         wf_service.trg_validate(uid, 'mrp.production', production_id, 'button_produce_done', cr)
655         return True
656
657     def _costs_generate(self, cr, uid, production):
658         amount = 0.0
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:
665                     amount += value
666                     self.pool.get('account.analytic.line').create(cr, uid, {
667                         'name': wc_line.name+' (H)',
668                         'amount': value,
669                         'account_id': account,
670                         'general_account_id': wc.costs_general_account_id.id,
671                         'journal_id': wc.costs_journal_id.id,
672                         'code': wc.code
673                     } )
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:
678                     amount += value
679                     self.pool.get('account.analytic.line').create(cr, uid, {
680                         'name': wc_line.name+' (C)',
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         return amount
688
689     def action_in_production(self, cr, uid, ids):
690         move_ids = []        
691         self.write(cr, uid, ids, {'state': 'in_production','date_start':time.strftime('%Y-%m-%d %H:%M:%S')})
692         return True
693
694     def test_if_product(self, cr, uid, ids):
695         res = True
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]):
699                     res = False
700         return res
701
702     def _get_auto_picking(self, cr, uid, production):
703         return True
704
705     def action_confirm(self, cr, uid, ids):
706         picking_id=False
707         proc_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]
712             routing_loc = None
713             pick_type = 'internal'
714             address_id = False
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':
718                     pick_type = 'out'
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, {
723                 'name': pick_name,
724                 'origin': (production.origin or '').split(':')[0] +':'+production.name,
725                 'type': pick_type,
726                 'move_type': 'one',
727                 'state': 'auto',
728                 'address_id': address_id,
729                 'auto_picking': self._get_auto_picking(cr, uid, production),
730                 'company_id': production.company_id.id,
731             })
732
733             source = production.product_id.product_tmpl_id.property_stock_production.id
734             data = {
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,
745                 'state': 'waiting',
746                 'company_id': production.company_id.id,
747             }
748             res_final_id = self.pool.get('stock.move').create(cr, uid, data)
749
750             self.write(cr, uid, [production.id], {'move_created_ids': [(6, 0, [res_final_id])]})
751             moves = []
752             for line in production.product_lines:
753                 move_id=False
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,
767                         'state': 'waiting',
768                         'company_id': production.company_id.id,
769                     })
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,
783                         'state': 'waiting',
784                         'company_id': production.company_id.id,
785                     })
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,
797                     'move_id': move_id,
798                     'company_id': production.company_id.id,
799                 })
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'})
806         return picking_id
807
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)])
811         return True
812
813 mrp_production()
814
815 class mrp_production_workcenter_line(osv.osv):
816     _name = 'mrp.production.workcenter.line'
817     _description = 'Work Orders'
818     _order = 'sequence'
819     _columns = {
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'),
826     }
827     _defaults = {
828         'sequence': lambda *a: 1,
829         'hour': lambda *a: 0,
830         'cycle': lambda *a: 0,
831     }
832 mrp_production_workcenter_line()
833
834 class mrp_production_product_line(osv.osv):
835     _name = 'mrp.production.product.line'
836     _description = 'Production scheduled products'
837     _columns = {
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),
845     }
846 mrp_production_product_line()
847
848 # ------------------------------------------------------------------
849 # Procurement
850 # ------------------------------------------------------------------
851 #
852 # Produce, Buy or Find products and place a move
853 #     then wizard for picking lists & move
854 #
855 class mrp_procurement(osv.osv):
856     _name = "mrp.procurement"
857     _description = "Procurement"
858     _order = 'priority,date_planned'
859     _columns = {
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'),
873
874         'bom_id': fields.many2one('mrp.bom', 'BoM', ondelete='cascade', select=True),
875
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."),
881
882         'purchase_id': fields.many2one('purchase.order', 'Purchase Order'),
883         'note': fields.text('Note'),
884
885         'property_ids': fields.many2many('mrp.property', 'mrp_procurement_property_rel', 'procurement_id','property_id', 'Properties'),
886
887         'message': fields.char('Latest error', size=64, help="Exception occurred while computing procurement orders."),
888         'state': fields.selection([
889             ('draft','Draft'),
890             ('confirmed','Confirmed'),
891             ('exception','Exception'),
892             ('running','Running'),
893             ('cancel','Cancel'),
894             ('ready','Ready'),
895             ('done','Done'),
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),
901     }
902     _defaults = {
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)
909     }
910
911     def unlink(self, cr, uid, ids, context=None):
912         procurements = self.read(cr, uid, ids, ['state'])
913         unlink_ids = []
914         for s in procurements:
915             if s['state'] in ['draft','cancel']:
916                 unlink_ids.append(s['id'])
917             else:
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)
920
921     def onchange_product_id(self, cr, uid, ids, product_id, context={}):
922         if product_id:
923             w=self.pool.get('product.product').browse(cr,uid,product_id, context)
924             v = {
925                 'product_uom':w.uom_id.id,
926                 'product_uos':w.uos_id and w.uos_id.id or w.uom_id.id
927             }
928             return {'value': v}
929         return {}
930
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'):
934                 return True
935         return False
936
937     def check_move_cancel(self, cr, uid, ids, context={}):
938         res = True
939         ok = False
940         for procurement in self.browse(cr, uid, ids, context):
941             if procurement.move_id:
942                 ok = True
943                 if not procurement.move_id.state=='cancel':
944                     res = False
945         return res and ok
946
947     def check_move_done(self, cr, uid, ids, context={}):
948         res = True
949         for proc in self.browse(cr, uid, ids, context):
950             if proc.move_id:
951                 if not proc.move_id.state=='done':
952                     res = False
953         return res
954
955     #
956     # This method may be overrided by objects that override mrp.procurment
957     # for computing their own purpose
958     #
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
963         return False
964
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
969         return False
970
971     #
972     # Return the quantity of product shipped/produced/served, wich may be
973     # different from the planned quantity
974     #
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)
978         if not result:
979             result = proc.product_qty
980         return result
981
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)
985         if not result:
986             result = proc.product_uom.id
987         return result
988
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':
992                 return True
993         return False
994
995     def check_produce_service(self, cr, uid, procurement, context=[]):
996         return True
997
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)
1001         if not bom_id:
1002             cr.execute('update mrp_procurement set message=%s where id=%s', (_('No BoM defined for this product !'), procurement.id))
1003             return False
1004         return True
1005
1006     def check_make_to_stock(self, cr, uid, ids, context={}):
1007         ok = True
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)
1011             else:
1012                 ok = ok and self._check_make_to_stock_product(cr, uid, procurement, context)
1013         return ok
1014
1015     def check_produce(self, cr, uid, ids, context={}):
1016         res = True
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:
1024                             return True
1025                 return False
1026             if procurement.product_id.product_tmpl_id.type=='service':
1027                 res = res and self.check_produce_service(cr, uid, procurement, context)
1028             else:
1029                 res = res and self.check_produce_product(cr, uid, procurement, context)
1030             if not res:
1031                 return False
1032         return res
1033
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':
1038                 return False
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))
1041                 return False
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:
1045                     return False
1046             address_id = self.pool.get('res.partner').address_get(cr, uid, [partner.id], ['delivery'])['delivery']
1047             if not address_id:
1048                 cr.execute('update mrp_procurement set message=%s where id=%s', (_('No address defined for the supplier'), procurement.id))
1049                 return False
1050         return True
1051
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':
1055                 return True
1056         return False
1057
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,
1077                     })
1078                     self.write(cr, uid, [procurement.id], {'move_id': id, 'close_move':1})
1079                 else:
1080                     # TODO: check this
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':''})
1084         return True
1085
1086     def action_move_assigned(self, cr, uid, ids, context={}):
1087         self.write(cr, uid, ids, {'state':'running','message':_('from stock: products assigned.')})
1088         return True
1089
1090     def _check_make_to_stock_service(self, cr, uid, procurement, context={}):
1091         return True
1092
1093     def _check_make_to_stock_product(self, cr, uid, procurement, context={}):
1094         ok = True
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))
1102         return ok
1103
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'})
1107         return True
1108
1109     def action_produce_assign_product(self, cr, uid, ids, context={}):
1110         """
1111         @summary : This is action which call from workflow to assign production order to procuments
1112         @return  : True
1113         """
1114         res = self.make_mo(cr, uid, ids, context=context)
1115         return 1 #TO CHECK: why workflow is generated error if return True
1116
1117     def make_mo(self, cr, uid, ids, context={}):
1118         """
1119         @summary : Make Manufecturing(production) order from procurement
1120         
1121         @return : New created Production Orders procurement wise 
1122         """
1123         res = {}
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,
1143             })
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})
1152         return res
1153     
1154     def action_po_assign(self, cr, uid, ids, context={}):
1155         """
1156         @summary : This is action which call from workflow to assign purchase order to procuments
1157         @return  : True
1158         """
1159         res = self.make_po(cr, uid, ids, context=context)
1160         return 1 #TO CHECK: why workflow is generated error if return True
1161
1162     def make_po(self, cr, uid, ids, context={}):
1163         """
1164         @summary : Make purchase order from procurement
1165         
1166         @return : New created Purchase Orders procurement wise
1167         """
1168         res = {}
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
1176
1177             uom_id = procurement.product_id.uom_po_id.id
1178
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)
1182
1183             price = self.pool.get('product.pricelist').price_get(cr, uid, [pricelist_id], procurement.product_id.id, qty, False, {'uom': uom_id})[pricelist_id]
1184
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
1188
1189             #Passing partner_id to context for purchase order line integrity of Line name
1190             context.update({'lang':partner.lang, 'partner_id':partner_id})
1191
1192             product=self.pool.get('product.product').browse(cr,uid,procurement.product_id.id,context=context)
1193
1194             line = {
1195                 'name': product.partner_ref,
1196                 'product_qty': qty,
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,
1203             }
1204
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)
1207             line.update({
1208                 'taxes_id':[(6,0,taxes)]
1209             })
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
1219             })
1220             res[procurement.id] = purchase_id
1221             self.write(cr, uid, [procurement.id], {'state':'running', 'purchase_id':purchase_id})
1222         return res
1223
1224     def action_cancel(self, cr, uid, ids):
1225         todo = []
1226         todo2 = []
1227         for proc in self.browse(cr, uid, ids):
1228             if proc.close_move:
1229                 if proc.move_id.state not in ('done','cancel'):
1230                     todo2.append(proc.move_id.id)
1231             else:
1232                 if proc.move_id and proc.move_id.state=='waiting':
1233                     todo.append(proc.move_id.id)
1234         if len(todo2):
1235             self.pool.get('stock.move').action_cancel(cr, uid, todo2)
1236         if len(todo):
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")
1240         for id in ids:
1241             wf_service.trg_trigger(uid, 'mrp.procurement', id, cr)
1242         return True
1243
1244     def action_check_finnished(self, cr, uid, ids):
1245         return self.check_move_done(cr, uid, ids)
1246
1247     def action_check(self, cr, uid, ids):
1248         ok = False
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])
1252                 ok = True
1253         return ok
1254
1255     def action_ready(self, cr, uid, ids):
1256         res = self.write(cr, uid, ids, {'state':'ready'})
1257         return res
1258
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")
1266         for id in ids:
1267             wf_service.trg_trigger(uid, 'mrp.procurement', id, cr)
1268         return res
1269
1270     def run_scheduler(self, cr, uid, automatic=False, use_new_cursor=False, context=None):
1271         '''
1272         use_new_cursor: False or the dbname
1273         '''
1274         if not context:
1275             context={}
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)
1279 mrp_procurement()
1280 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
1281