[FIX] Schedule jobs even if their next time has passed.
[odoo/odoo.git] / addons / mrp / mrp.py
index 828998c..f500d4f 100644 (file)
@@ -27,6 +27,7 @@ import ir
 import netsvc
 import time
 from mx import DateTime
+from tools.translate import _
 
 #----------------------------------------------------------
 # Workcenters
@@ -128,7 +129,7 @@ class mrp_routing_workcenter(osv.osv):
         'cycle_nbr': fields.float('Number of Cycle', required=True,
             help="A cycle is defined in the workcenter definition."),
         'hour_nbr': fields.float('Number of Hours', required=True),
-        'routing_id': fields.many2one('mrp.routing', 'Parent Routing', select=True),
+        'routing_id': fields.many2one('mrp.routing', 'Parent Routing', select=True, ondelete='cascade'),
         'note': fields.text('Description')
     }
     _defaults = {
@@ -215,7 +216,7 @@ class mrp_bom(osv.osv):
     def _check_recursion(self, cr, uid, ids):
         level = 500
         while len(ids):
-            cr.execute('select distinct bom_id from mrp_bom where id in ('+','.join(map(str,ids))+')')
+            cr.execute('select distinct bom_id from mrp_bom where id in %s', (tuple(ids),))
             ids = filter(None, map(lambda x:x[0], cr.fetchall()))
             if not level:
                 return False
@@ -299,21 +300,6 @@ class mrp_bom(osv.osv):
                 result2 = result2 + res[1]
         return result, result2
 
-    def set_indices(self, cr, uid, ids, context = {}):
-        if not ids or (ids and not ids[0]):
-            return True
-        res = self.read(cr, uid, ids, ['revision_ids', 'revision_type'])
-        rev_ids = res[0]['revision_ids']
-        idx = 1
-        new_idx = []
-        for rev_id in rev_ids:
-            if res[0]['revision_type'] == 'numeric':
-                self.pool.get('mrp.bom.revision').write(cr, uid, [rev_id], {'indice' : idx})
-            else:
-                self.pool.get('mrp.bom.revision').write(cr, uid, [rev_id], {'indice' : "%c"%(idx+96,)})
-            idx+=1
-        return True
-
 mrp_bom()
 
 class mrp_bom_revision(osv.osv):
@@ -346,27 +332,6 @@ class mrp_production(osv.osv):
     _description = 'Production'
     _date_name  = 'date_planned'
 
-    def _get_sale_order(self,cr,uid,ids,field_name=False):
-        move_obj=self.pool.get('stock.move')
-        def get_parent_move(move_id):
-            move = move_obj.browse(cr,uid,move_id)
-            if move.move_dest_id:
-                return get_parent_move(move.move_dest_id.id)
-            return move_id
-        productions=self.read(cr,uid,ids,['id','move_prod_id'])
-        res={}
-        for production in productions:
-            res[production['id']]=False
-            if production.get('move_prod_id',False):
-                parent_move_line=get_parent_move(production['move_prod_id'][0])
-                if parent_move_line:
-                    move = move_obj.browse(cr,uid,parent_move_line)
-                    if field_name=='name':
-                        res[production['id']]=move.sale_line_id and move.sale_line_id.order_id.name or False
-                    if field_name=='client_order_ref':
-                        res[production['id']]=move.sale_line_id and move.sale_line_id.order_id.client_order_ref or False
-        return res
-
     def _production_calc(self, cr, uid, ids, prop, unknow_none, context={}):
         result = {}
         for prod in self.browse(cr, uid, ids, context=context):
@@ -379,35 +344,36 @@ class mrp_production(osv.osv):
                 result[prod.id]['cycle_total'] += wc.cycle
         return result
 
+    def _production_date_end(self, cr, uid, ids, prop, unknow_none, context={}):
+        result = {}
+        for prod in self.browse(cr, uid, ids, context=context):
+            result[prod.id] = prod.date_planned
+        return result
+
     def _production_date(self, cr, uid, ids, prop, unknow_none, context={}):
         result = {}
         for prod in self.browse(cr, uid, ids, context=context):
             result[prod.id] = prod.date_planned[:10]
         return result
 
-    def _sale_name_calc(self, cr, uid, ids, prop, unknow_none, unknow_dict):
-        return self._get_sale_order(cr,uid,ids,field_name='name')
-
-    def _sale_ref_calc(self, cr, uid, ids, prop, unknow_none, unknow_dict):
-        return self._get_sale_order(cr,uid,ids,field_name='client_order_ref')
-
     _columns = {
         'name': fields.char('Reference', size=64, required=True),
         'origin': fields.char('Origin', size=64),
         'priority': fields.selection([('0','Not urgent'),('1','Normal'),('2','Urgent'),('3','Very Urgent')], 'Priority'),
 
         'product_id': fields.many2one('product.product', 'Product', required=True, domain=[('type','<>','service')]),
-        'product_qty': fields.float('Product Qty', required=True),
-        'product_uom': fields.many2one('product.uom', 'Product UOM', required=True),
-        'product_uos_qty': fields.float('Product Qty'),
-        'product_uos': fields.many2one('product.uom', 'Product UOM'),
+        'product_qty': fields.float('Product Qty', required=True, states={'draft':[('readonly',False)]}, readonly=True),
+        'product_uom': fields.many2one('product.uom', 'Product UOM', required=True, states={'draft':[('readonly',False)]}, readonly=True),
+        'product_uos_qty': fields.float('Product UoS Qty', states={'draft':[('readonly',False)]}, readonly=True),
+        'product_uos': fields.many2one('product.uom', 'Product UoS', states={'draft':[('readonly',False)]}, readonly=True),
 
         'location_src_id': fields.many2one('stock.location', 'Raw Materials Location', required=True,
             help="Location where the system will look for products used in raw materials."),
         'location_dest_id': fields.many2one('stock.location', 'Finished Products Location', required=True,
             help="Location where the system will stock the finished products."),
 
-        'date_planned_date': fields.function(_production_date, method=True, type='date', string='Planned Date'),
+        'date_planned_end': fields.function(_production_date_end, method=True, type='date', string='Scheduled End'),
+        'date_planned_date': fields.function(_production_date, method=True, type='date', string='Scheduled Date'),
         'date_planned': fields.datetime('Scheduled date', required=True, select=1),
         'date_start': fields.datetime('Start Date'),
         'date_finnished': fields.datetime('End Date'),
@@ -428,8 +394,6 @@ class mrp_production(osv.osv):
         'hour_total': fields.function(_production_calc, method=True, type='float', string='Total Hours', multi='workorder'),
         'cycle_total': fields.function(_production_calc, method=True, type='float', string='Total Cycles', multi='workorder'),
 
-        'sale_name': fields.function(_sale_name_calc, method=True, type='char', string='Sale Name'),
-        'sale_ref': fields.function(_sale_ref_calc, method=True, type='char', string='Sale Ref'),
     }
     _defaults = {
         'priority': lambda *a: '1',
@@ -455,6 +419,8 @@ class mrp_production(osv.osv):
         default.update({
             'name': self.pool.get('ir.sequence').get(cr, uid, 'mrp.production'),
             'move_lines' : [],
+            'move_created_ids': [],
+            'state': 'draft'
         })
         return super(mrp_production, self).copy(cr, uid, id, default, context)
 
@@ -473,6 +439,14 @@ class mrp_production(osv.osv):
         result = {'product_uom':uom}
         return {'value':result}
 
+    def bom_id_change(self, cr, uid, ids, product):
+        if not product:
+            return {}
+        res = self.pool.get('mrp.bom').read(cr, uid, [product], ['routing_id'])[0]
+        routing_id = res['routing_id'] and res['routing_id'][0]
+        result = {'routing_id':routing_id}
+        return {'value':result}
+
     def action_picking_except(self, cr, uid, ids):
         self.write(cr, uid, ids, {'state':'picking_except'})
         return True
@@ -492,7 +466,7 @@ class mrp_production(osv.osv):
                     self.write(cr, uid, [production.id], {'bom_id': bom_id, 'routing_id': routing_id})
 
             if not bom_id:
-                raise osv.except_osv('Error', "Couldn't find bill of material for product")
+                raise osv.except_osv(_('Error'), _("Couldn't find bill of material for product"))
 
             #if bom_point.routing_id and bom_point.routing_id.location_id:
             #   self.write(cr, uid, [production.id], {'location_src_id': bom_point.routing_id.location_id.id})
@@ -529,7 +503,7 @@ class mrp_production(osv.osv):
 
     #TODO Review materials in function in_prod and prod_end.
     def action_production_end(self, cr, uid, ids):
-        move_ids = []
+#        move_ids = []
         for production in self.browse(cr, uid, ids):
             for res in production.move_lines:
                 for move in production.move_created_ids:
@@ -537,7 +511,7 @@ class mrp_production(osv.osv):
                     cr.execute('INSERT INTO stock_move_history_ids \
                             (parent_id, child_id) VALUES (%s,%s)',
                             (res.id, move.id))
-                move_ids.append(res.id)
+#                move_ids.append(res.id)
             vals= {'state':'confirmed'}
             new_moves = [x.id for x in production.move_created_ids]
             self.pool.get('stock.move').write(cr, uid, new_moves, vals)
@@ -547,7 +521,7 @@ class mrp_production(osv.osv):
             self.pool.get('stock.move').check_assign(cr, uid, new_moves)
             self.pool.get('stock.move').action_done(cr, uid, new_moves)
             self._costs_generate(cr, uid, production)
-        self.pool.get('stock.move').action_done(cr, uid, move_ids)
+#        self.pool.get('stock.move').action_done(cr, uid, move_ids)
         self.write(cr,  uid, ids, {'state': 'done'})
         return True
 
@@ -720,14 +694,15 @@ stock_move()
 
 class mrp_production_workcenter_line(osv.osv):
     _name = 'mrp.production.workcenter.line'
-    _description = 'Production workcenters used'
+    _description = 'Work Orders'
+    _order = 'sequence'
     _columns = {
-        'name': fields.char('Name', size=64, required=True),
+        'name': fields.char('Work Order', size=64, required=True),
         'workcenter_id': fields.many2one('mrp.workcenter', 'Workcenter', required=True),
         'cycle': fields.float('Nbr of cycle', digits=(16,2)),
         'hour': fields.float('Nbr of hour', digits=(16,2)),
         'sequence': fields.integer('Sequence', required=True),
-        'production_id': fields.many2one('mrp.production', 'Production Order', select=True),
+        'production_id': fields.many2one('mrp.production', 'Production Order', select=True, ondelete='cascade'),
     }
     _defaults = {
         'sequence': lambda *a: 1,
@@ -769,10 +744,10 @@ class mrp_procurement(osv.osv):
         'date_planned': fields.datetime('Scheduled date', required=True),
         'date_close': fields.datetime('Date Closed'),
         'product_id': fields.many2one('product.product', 'Product', required=True),
-        'product_qty': fields.float('Quantity', required=True),
-        'product_uom': fields.many2one('product.uom', 'Product UoM', required=True),
-        'product_uos_qty': fields.float('UoS Quantity'),
-        'product_uos': fields.many2one('product.uom', 'Product UoS'),
+        'product_qty': fields.float('Quantity', required=True, states={'draft':[('readonly',False)]}, readonly=True),
+        'product_uom': fields.many2one('product.uom', 'Product UoM', required=True, states={'draft':[('readonly',False)]}, readonly=True),
+        'product_uos_qty': fields.float('UoS Quantity', states={'draft':[('readonly',False)]}, readonly=True),
+        'product_uos': fields.many2one('product.uom', 'Product UoS', states={'draft':[('readonly',False)]}, readonly=True),
         'move_id': fields.many2one('stock.move', 'Reservation', ondelete='set null'),
 
         'bom_id': fields.many2one('mrp.bom', 'BoM', ondelete='cascade', select=True),
@@ -797,7 +772,8 @@ class mrp_procurement(osv.osv):
             ('cancel','Cancel'),
             ('ready','Ready'),
             ('done','Done'),
-            ('waiting','Waiting')], 'Status')
+            ('waiting','Waiting')], 'Status', required=True),
+        'note' : fields.text('Note'),
     }
     _defaults = {
         'state': lambda *a: 'draft',
@@ -833,13 +809,26 @@ class mrp_procurement(osv.osv):
                 return True
         return False
 
+    def get_phantom_bom_id(self, cr, uid, ids, context=None):
+        for procurement in self.browse(cr, uid, ids, context=context):
+            if procurement.move_id and procurement.move_id.product_id.supply_method=='produce' \
+                 and procurement.move_id.product_id.procure_method=='make_to_order':
+                    phantom_bom_id = self.pool.get('mrp.bom').search(cr, uid, [
+                        ('product_id', '=', procurement.move_id.product_id.id),
+                        ('bom_id', '=', False),
+                        ('type', '=', 'phantom')]) 
+                    return phantom_bom_id 
+        return False
+
     def check_move_cancel(self, cr, uid, ids, context={}):
         res = True
+        ok = False
         for procurement in self.browse(cr, uid, ids, context):
             if procurement.move_id:
+                ok = True
                 if not procurement.move_id.state=='cancel':
                     res = False
-        return res
+        return res and ok
 
     def check_move_done(self, cr, uid, ids, context={}):
         res = True
@@ -954,6 +943,8 @@ class mrp_procurement(osv.osv):
 
     def action_confirm(self, cr, uid, ids, context={}):
         for procurement in self.browse(cr, uid, ids):
+            if procurement.product_qty <= 0.00:
+                raise osv.except_osv(_('Data Insufficient !'), _('Please check the Quantity of Procurement Order(s), it should not be less than 1!'))
             if procurement.product_id.type in ('product', 'consu'):
                 if not procurement.move_id:
                     source = procurement.location_id.id
@@ -971,13 +962,13 @@ class mrp_procurement(osv.osv):
                     })
                     self.write(cr, uid, [procurement.id], {'move_id': id, 'close_move':1})
                 else:
-                    # TODO: check this
-                    if procurement.procure_method=='make_to_stock' and procurement.move_id.state in ('waiting',):
-                        id = self.pool.get('stock.move').write(cr, uid, [procurement.move_id.id], {'state':'confirmed'})
+                    if procurement.procure_method=='make_to_stock' and procurement.move_id.state in ('draft','waiting',):
+                        # properly call action_confirm() on stock.move to abide by the location chaining etc.
+                        id = self.pool.get('stock.move').action_confirm(cr, uid, [procurement.move_id.id], context=context)
         self.write(cr, uid, ids, {'state':'confirmed','message':''})
         return True
 
-    def action_move_assigned(self, cr, uid, ids):
+    def action_move_assigned(self, cr, uid, ids, context={}):
         self.write(cr, uid, ids, {'state':'running','message':_('from stock: products assigned.')})
         return True
 
@@ -1026,6 +1017,8 @@ class mrp_procurement(osv.osv):
                     [produce_id], properties=[x.id for x in procurement.property_ids])
             wf_service = netsvc.LocalService("workflow")
             wf_service.trg_validate(uid, 'mrp.production', produce_id, 'button_confirm', cr)
+            self.pool.get('stock.move').write(cr, uid, [res_id],
+                    {'location_id':procurement.location_id.id})
         return produce_id
 
     def action_po_assign(self, cr, uid, ids, context={}):
@@ -1048,11 +1041,15 @@ class mrp_procurement(osv.osv):
 
             newdate = DateTime.strptime(procurement.date_planned, '%Y-%m-%d %H:%M:%S')
             newdate = newdate - DateTime.RelativeDateTime(days=company.po_lead)
-            context.update({'lang':partner.lang})
+            newdate = newdate - procurement.product_id.seller_ids[0].delay
+
+            #Passing partner_id to context for purchase order line integrity of Line name
+            context.update({'lang':partner.lang, 'partner_id':partner_id})
+            
             product=self.pool.get('product.product').browse(cr,uid,procurement.product_id.id,context=context)
 
             line = {
-                'name': product.name,
+                'name': product.partner_ref,
                 'product_qty': qty,
                 'product_id': procurement.product_id.id,
                 'product_uom': uom_id,
@@ -1081,17 +1078,22 @@ class mrp_procurement(osv.osv):
 
     def action_cancel(self, cr, uid, ids):
         todo = []
+        todo2 = []
         for proc in self.browse(cr, uid, ids):
-            if proc.move_id and proc.move_id.state=='waiting':
-                todo.append(proc.move_id.id)
+            if proc.close_move and proc.move_id:
+                if proc.move_id.state not in ('done','cancel'):
+                    todo2.append(proc.move_id.id)
+            else:
+                if proc.move_id and proc.move_id.state=='waiting':
+                    todo.append(proc.move_id.id)
+        if len(todo2):
+            self.pool.get('stock.move').action_cancel(cr, uid, todo2)
         if len(todo):
             self.pool.get('stock.move').write(cr, uid, todo, {'state':'assigned'})
         self.write(cr, uid, ids, {'state':'cancel'})
-
         wf_service = netsvc.LocalService("workflow")
         for id in ids:
             wf_service.trg_trigger(uid, 'mrp.procurement', id, cr)
-
         return True
 
     def action_check_finnished(self, cr, uid, ids):
@@ -1139,10 +1141,10 @@ class stock_warehouse_orderpoint(osv.osv):
         'name': fields.char('Name', size=32, required=True),
         'active': fields.boolean('Active'),
         'logic': fields.selection([('max','Order to Max'),('price','Best price (not yet active!)')], 'Reordering Mode', required=True),
-        'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse', required=True),
-        'location_id': fields.many2one('stock.location', 'Location', required=True),
-        'product_id': fields.many2one('product.product', 'Product', required=True, domain=[('type','=','product')]),
-        'product_uom': fields.many2one('product.uom', 'Product UOM', required=True ),
+        'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse', required=True, ondelete="cascade"),
+        'location_id': fields.many2one('stock.location', 'Location', required=True, ondelete="cascade"),
+        'product_id': fields.many2one('product.product', 'Product', required=True, domain=[('type','=','product')], ondelete="cascade"),
+        'product_uom': fields.many2one('product.uom', 'Product UOM', required=True),
         'product_min_qty': fields.float('Min Quantity', required=True,
             help="When the virtual stock goes belong the Min Quantity, Open ERP generates "\
             "a procurement to bring the virtual stock to the Max Quantity."),
@@ -1151,7 +1153,7 @@ class stock_warehouse_orderpoint(osv.osv):
             "a procurement to bring the virtual stock to the Max Quantity."),
         'qty_multiple': fields.integer('Qty Multiple', required=True,
             help="The procurement quantity will by rounded up to this multiple."),
-        'procurement_id': fields.many2one('mrp.procurement', 'Purchase Order')
+        'procurement_id': fields.many2one('mrp.procurement', 'Purchase Order', ondelete="set null")
     }
     _defaults = {
         'active': lambda *a: 1,
@@ -1160,6 +1162,11 @@ class stock_warehouse_orderpoint(osv.osv):
         'name': lambda x,y,z,c: x.pool.get('ir.sequence').get(y,z,'mrp.warehouse.orderpoint') or '',
         'product_uom': lambda sel, cr, uid, context: context.get('product_uom', False),
     }
+    
+    _sql_constraints = [
+        ( 'qty_multiple_check', 'CHECK( qty_multiple > 0 )', _('Qty Multiple must be greater than zero.')),
+    ]
+    
     def onchange_warehouse_id(self, cr, uid, ids, warehouse_id, context={}):
         if warehouse_id:
             w=self.pool.get('stock.warehouse').browse(cr,uid,warehouse_id, context)
@@ -1202,7 +1209,6 @@ class StockMove(osv.osv):
                 factor = move.product_qty
                 bom_point = self.pool.get('mrp.bom').browse(cr, uid, bis[0])
                 res = self.pool.get('mrp.bom')._bom_explode(cr, uid, bom_point, factor, [])
-                dest = move.product_id.product_tmpl_id.property_stock_production.id
                 state = 'confirmed'
                 if move.state=='assigned':
                     state='assigned'
@@ -1214,10 +1220,8 @@ class StockMove(osv.osv):
                         'product_qty': line['product_qty'],
                         'product_uos': line['product_uos'],
                         'product_uos_qty': line['product_uos_qty'],
-                        'move_dest_id': move.id,
                         'state': state,
                         'name': line['name'],
-                        'location_dest_id': dest,
                         'move_history_ids': [(6,0,[move.id])],
                         'move_history_ids2': [(6,0,[])],
                         'procurements': []
@@ -1240,10 +1244,9 @@ class StockMove(osv.osv):
                     wf_service = netsvc.LocalService("workflow")
                     wf_service.trg_validate(uid, 'mrp.procurement', proc_id, 'button_confirm', cr)
                 self.pool.get('stock.move').write(cr, uid, [move.id], {
-                    'location_id': move.location_dest_id.id,
+                    'location_id': move.location_dest_id.id, # src and dest locations identical to have correct inventory, dummy move
                     'auto_validate': True,
                     'picking_id': False,
-                    'location_id': dest,
                     'state': 'waiting'
                 })
                 for m in self.pool.get('mrp.procurement').search(cr, uid, [('move_id','=',move.id)], context):