[IMP] Query should pass through orm and as such the refreshes can be removed in the...
[odoo/odoo.git] / addons / procurement / procurement.py
index 920cd73..085d620 100644 (file)
 ##############################################################################
 
 import time
+from psycopg2 import OperationalError
 
-from datetime import datetime
-from dateutil.relativedelta import relativedelta
-
+from openerp import SUPERUSER_ID
 from openerp.osv import fields, osv
 import openerp.addons.decimal_precision as dp
 from openerp.tools.translate import _
 import openerp
 
+PROCUREMENT_PRIORITIES = [('0', 'Not urgent'), ('1', 'Normal'), ('2', 'Urgent'), ('3', 'Very Urgent')]
+
 class procurement_group(osv.osv):
     '''
-    The procurement requirement class is used to group products together
+    The procurement group class is used to group products together
     when computing procurements. (tasks, physical products, ...)
 
     The goal is that when you have one sale order of several products
@@ -56,16 +57,15 @@ class procurement_group(osv.osv):
     _description = 'Procurement Requisition'
     _order = "id desc"
     _columns = {
-        'name': fields.char('Reference', required=True), 
+        'name': fields.char('Reference', required=True),
         'move_type': fields.selection([
             ('direct', 'Partial'), ('one', 'All at once')],
             'Delivery Method', required=True),
-        'partner_id': fields.many2one('res.partner', string = 'Partner'), #Sale should pass it here 
-        'procurement_ids': fields.one2many('procurement.order', 'group_id', 'Procurements'), 
+        'procurement_ids': fields.one2many('procurement.order', 'group_id', 'Procurements'),
     }
     _defaults = {
         'name': lambda self, cr, uid, c: self.pool.get('ir.sequence').get(cr, uid, 'procurement.group') or '',
-        'move_type': lambda self, cr, uid, c: 'one'
+        'move_type': lambda self, cr, uid, c: 'direct'
     }
 
 class procurement_rule(osv.osv):
@@ -82,6 +82,7 @@ class procurement_rule(osv.osv):
     _columns = {
         'name': fields.char('Name', required=True,
             help="This field will fill the packing origin and the name of its moves"),
+        'active': fields.boolean('Active', help="If unchecked, it will allow you to hide the rule without removing it."),
         'group_propagation_option': fields.selection([('none', 'Leave Empty'), ('propagate', 'Propagate'), ('fixed', 'Fixed')], string="Propagation of Procurement Group"),
         'group_id': fields.many2one('procurement.group', 'Fixed Procurement Group'),
         'action': fields.selection(selection=lambda s, cr, uid, context=None: s._get_action(cr, uid, context=context),
@@ -93,6 +94,7 @@ class procurement_rule(osv.osv):
     _defaults = {
         'group_propagation_option': 'propagate',
         'sequence': 20,
+        'active': True,
     }
 
 
@@ -108,17 +110,17 @@ class procurement_order(osv.osv):
     _columns = {
         'name': fields.text('Description', required=True),
 
-        'origin': fields.char('Source Document', size=64,
+        'origin': fields.char('Source Document',
             help="Reference of the document that created this Procurement.\n"
-            "This is automatically completed by OpenERP."),
+            "This is automatically completed by Odoo."),
         'company_id': fields.many2one('res.company', 'Company', required=True),
 
         # These two fields are used for shceduling
-        'priority': fields.selection([('0', 'Not urgent'), ('1', 'Normal'), ('2', 'Urgent'), ('3', 'Very Urgent')], 'Priority', required=True, select=True, track_visibility='onchange'),
+        'priority': fields.selection(PROCUREMENT_PRIORITIES, 'Priority', required=True, select=True, track_visibility='onchange'),
         'date_planned': fields.datetime('Scheduled Date', required=True, select=True, track_visibility='onchange'),
 
         'group_id': fields.many2one('procurement.group', 'Procurement Group'),
-        'rule_id': fields.many2one('procurement.rule', 'Rule', track_visibility='onchange'),
+        'rule_id': fields.many2one('procurement.rule', 'Rule', track_visibility='onchange', help="Chosen rule for the procurement resolution. Usually chosen by the system but can be manually set by the procurement manager to force an unusual behavior."),
 
         'product_id': fields.many2one('product.product', 'Product', required=True, states={'confirmed': [('readonly', False)]}, readonly=True),
         'product_qty': fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True, states={'confirmed': [('readonly', False)]}, readonly=True),
@@ -133,7 +135,7 @@ class procurement_order(osv.osv):
             ('exception', 'Exception'),
             ('running', 'Running'),
             ('done', 'Done')
-        ], 'Status', required=True, track_visibility='onchange'),
+        ], 'Status', required=True, track_visibility='onchange', copy=False),
     }
 
     _defaults = {
@@ -147,7 +149,7 @@ class procurement_order(osv.osv):
         procurements = self.read(cr, uid, ids, ['state'], context=context)
         unlink_ids = []
         for s in procurements:
-            if s['state'] in ['draft', 'cancel']:
+            if s['state'] == 'cancel':
                 unlink_ids.append(s['id'])
             else:
                 raise osv.except_osv(_('Invalid Action!'),
@@ -159,11 +161,9 @@ class procurement_order(osv.osv):
         This function returns an action that display existing procurement orders
         of same procurement group of given ids.
         '''
-        mod_obj = self.pool.get('ir.model.data')
         act_obj = self.pool.get('ir.actions.act_window')
-        result = mod_obj.get_object_reference(cr, uid, 'procurement', 'do_view_procurements')
-        id = result and result[1] or False
-        result = act_obj.read(cr, uid, [id], context=context)[0]
+        action_id = self.pool.get('ir.model.data').xmlid_to_res_id(cr, uid, 'procurement.do_view_procurements', raise_if_not_found=True)
+        result = act_obj.read(cr, uid, [action_id], context=context)[0]
         group_ids = set([proc.group_id.id for proc in self.browse(cr, uid, ids, context=context) if proc.group_id])
         result['domain'] = "[('group_id','in',[" + ','.join(map(str, list(group_ids))) + "])]"
         return result
@@ -194,27 +194,48 @@ class procurement_order(osv.osv):
     def reset_to_confirmed(self, cr, uid, ids, context=None):
         return self.write(cr, uid, ids, {'state': 'confirmed'}, context=context)
 
-    def run(self, cr, uid, ids, context=None):
-        for procurement in self.browse(cr, uid, ids, context=context):
+    def run(self, cr, uid, ids, autocommit=False, context=None):
+        for procurement_id in ids:
+            #we intentionnaly do the browse under the for loop to avoid caching all ids which would be resource greedy
+            #and useless as we'll make a refresh later that will invalidate all the cache (and thus the next iteration
+            #will fetch all the ids again) 
+            procurement = self.browse(cr, uid, procurement_id, context=context)
             if procurement.state not in ("running", "done"):
-                if self._assign(cr, uid, procurement, context=context):
-                    procurement.refresh()
-                    res = self._run(cr, uid, procurement, context=context or {})
-                    if res:
-                        self.write(cr, uid, [procurement.id], {'state': 'running'}, context=context)
+                try:
+                    if self._assign(cr, uid, procurement, context=context):
+                        res = self._run(cr, uid, procurement, context=context or {})
+                        if res:
+                            self.write(cr, uid, [procurement.id], {'state': 'running'}, context=context)
+                        else:
+                            self.write(cr, uid, [procurement.id], {'state': 'exception'}, context=context)
                     else:
+                        self.message_post(cr, uid, [procurement.id], body=_('No rule matching this procurement'), context=context)
                         self.write(cr, uid, [procurement.id], {'state': 'exception'}, context=context)
-                else:
-                    self.message_post(cr, uid, [procurement.id], body=_('No rule matching this procurement'), context=context)
-                    self.write(cr, uid, [procurement.id], {'state': 'exception'}, context=context)
+                    if autocommit:
+                        cr.commit()
+                except OperationalError:
+                    if autocommit:
+                        cr.rollback()
+                        continue
+                    else:
+                        raise
         return True
 
-    def check(self, cr, uid, ids, context=None):
+    def check(self, cr, uid, ids, autocommit=False, context=None):
         done_ids = []
         for procurement in self.browse(cr, uid, ids, context=context):
-            result = self._check(cr, uid, procurement, context=context)
-            if result:
-                done_ids.append(procurement.id)
+            try:
+                result = self._check(cr, uid, procurement, context=context)
+                if result:
+                    done_ids.append(procurement.id)
+                if autocommit:
+                    cr.commit()
+            except OperationalError:
+                if autocommit:
+                    cr.rollback()
+                    continue
+                else:
+                    raise
         if done_ids:
             self.write(cr, uid, done_ids, {'state': 'done'}, context=context)
         return done_ids
@@ -250,6 +271,7 @@ class procurement_order(osv.osv):
     def _run(self, cr, uid, procurement, context=None):
         '''This method implements the resolution of the given procurement
             :param procurement: browse record
+            :returns: True if the resolution of the procurement was a success, False otherwise to set it in exception
         '''
         return True
 
@@ -263,15 +285,17 @@ class procurement_order(osv.osv):
     #
     # Scheduler
     #
-    def run_scheduler(self, cr, uid, use_new_cursor=False, context=None):
+    def run_scheduler(self, cr, uid, use_new_cursor=False, company_id = False, context=None):
         '''
-        Call the scheduler to check the procurement order
+        Call the scheduler to check the procurement order. This is intented to be done for all existing companies at
+        the same time, so we're running all the methods as SUPERUSER to avoid intercompany and access rights issues.
 
         @param self: The object pointer
         @param cr: The current row, from the database cursor,
         @param uid: The current user ID for security checks
         @param ids: List of selected IDs
-        @param use_new_cursor: False or the dbname
+        @param use_new_cursor: if set, use a dedicated cursor and auto-commit after processing each procurement.
+            This is appropriate for batch jobs only.
         @param context: A standard dictionary for contextual values
         @return:  Dictionary of values
         '''
@@ -279,28 +303,36 @@ class procurement_order(osv.osv):
             context = {}
         try:
             if use_new_cursor:
-                cr = openerp.registry(use_new_cursor).db.cursor()
-
-            company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id
-            maxdate = (datetime.today() + relativedelta(days=company.schedule_range)).strftime('%Y-%m-%d %H:%M:%S')
+                cr = openerp.registry(cr.dbname).cursor()
 
             # Run confirmed procurements
+            dom = [('state', '=', 'confirmed')]
+            if company_id:
+                dom += [('company_id', '=', company_id)]
+            prev_ids = []
             while True:
-                ids = self.search(cr, uid, [('state', '=', 'confirmed'), ('date_planned', '<=', maxdate)], context=context)
-                if not ids:
+                ids = self.search(cr, SUPERUSER_ID, dom, context=context)
+                if not ids or prev_ids == ids:
                     break
-                self.run(cr, uid, ids, context=context)
+                else:
+                    prev_ids = ids
+                self.run(cr, SUPERUSER_ID, ids, autocommit=use_new_cursor, context=context)
                 if use_new_cursor:
                     cr.commit()
 
             # Check if running procurements are done
             offset = 0
+            dom = [('state', '=', 'running')]
+            if company_id:
+                dom += [('company_id', '=', company_id)]
+            prev_ids = []
             while True:
-                ids = self.search(cr, uid, [('state', '=', 'running'), ('date_planned', '<=', maxdate)], offset=offset, context=context)
-                if not ids:
+                ids = self.search(cr, SUPERUSER_ID, dom, offset=offset, context=context)
+                if not ids or prev_ids == ids:
                     break
-                done = self.check(cr, uid, ids, context=context)
-                offset += len(ids) - len(done)
+                else:
+                    prev_ids = ids
+                self.check(cr, SUPERUSER_ID, ids, autocommit=use_new_cursor, context=context)
                 if use_new_cursor:
                     cr.commit()
 
@@ -310,5 +342,6 @@ class procurement_order(osv.osv):
                     cr.close()
                 except Exception:
                     pass
+
         return {}
 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: