[WIP] Remove stock_fifo_lifo, check group_id gets created with procurement, reconcile...
authorJosse Colpaert <jco@openerp.com>
Thu, 11 Jul 2013 13:02:37 +0000 (15:02 +0200)
committerJosse Colpaert <jco@openerp.com>
Thu, 11 Jul 2013 13:02:37 +0000 (15:02 +0200)
bzr revid: jco@openerp.com-20130711130237-uh0qyib4xtg7o68g

addons/procurement/procurement.py
addons/sale/sale.py
addons/stock/procurement.py
addons/stock/report/report_stock_move.py
addons/stock/stock.py
addons/stock/stock_fifo_lifo.py [deleted file]
addons/stock/stock_view.xml
addons/stock_location/stock_location.py

index dbbd2f4..de92d48 100644 (file)
@@ -119,6 +119,8 @@ class procurement_order(osv.osv):
             ('running', 'Running'),
             ('done', 'Done')
         ], 'Status', required=True, track_visibility='onchange'),
+        'message': fields.text('Latest error', help="Exception occurred while computing procurement orders."),
+
 
     }
     _defaults = {
index f99b19b..1e2ae10 100644 (file)
@@ -684,7 +684,7 @@ class sale_order(osv.osv):
                 val['shipped'] = False
 
                 if (order.order_policy == 'manual'):
-                    for line in order.order_line:
+                    for line in order.order_line: 
                         if (not line.invoiced) and (line.state not in ('cancel', 'draft')):
                             val['state'] = 'manual'
                             break
index 3e59c97..b909fe5 100644 (file)
@@ -21,6 +21,7 @@
 
 from openerp.osv import fields, osv
 from openerp.tools.translate import _
+import openerp
 
 class procurement_group(osv.osv):
     _inherit = 'procurement.group'
@@ -86,7 +87,6 @@ class procurement_order(osv.osv):
                 return False
             move_obj = self.pool.get('stock.move')
             move_dict = self._run_move_create(cr, uid, procurement, context=context)
-
             move_id = move_obj.create(cr, uid, move_dict, context=context)
             move_obj.action_confirm(cr, uid, [move_id], context=context)
             self.write(cr, uid, [procurement.id], {'move_id': move_id}, context=context)
@@ -98,4 +98,44 @@ class procurement_order(osv.osv):
             return procurement.move_id.state == 'done'
         return super(procurement_order, self)._check(cr, uid, procurement, context)
 
+
+
+    #
+    # Scheduler
+    # When stock is installed, it should also check for the different 
+    #
+    #
+    def run_scheduler(self, cr, uid, use_new_cursor=False, context=None):
+        '''
+        Call the scheduler in order to 
+
+        @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 context: A standard dictionary for contextual values
+        @return:  Dictionary of values
+        '''
+        super(procurement_order, self).run_scheduler(cr, uid, use_new_cursor=use_new_cursor, context=context)
+        if context is None:
+            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
+            move_obj = self.pool.get('stock.move')
+            #Search all confirmed stock_moves and try to assign them
+            confirmed_ids = move_obj.search(cr, uid, [('state', '=', 'confirmed'), ('company_id','=', company.id)], context=context)
+            move_obj.action_assign(cr, uid, confirmed_ids, context=context)
+        finally:
+            if use_new_cursor:
+                try:
+                    cr.close()
+                except Exception:
+                    pass
+        return {}
+
+
 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
index c00031e..4302d08 100644 (file)
@@ -28,7 +28,6 @@ class report_stock_inventory(osv.osv):
     _name = "report.stock.inventory"
     _description = "Stock Statistics"
     _auto = False
-    _order = 'date desc'
     def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False):
         res = super(report_stock_inventory, self).read_group(cr, uid, domain, fields, groupby, offset=offset, limit=limit, context=context, orderby=orderby)
         product_obj = self.pool.get("product.product")
index b2ccbe1..8ac56c4 100644 (file)
@@ -225,13 +225,15 @@ class stock_quant(osv.osv):
         # result = self._quants_get(cr, uid, location, product, qty, domain+domain2, context=context)
         # qty = remaining quants qty - sum(result)
         domain = domain or [('qty','>',0.0)]
-        removal_strategy = self.pool.get('stock.location').get_removal_strategy(cr, uid, location, product, context=context) or 'fifo'
-        if removal_strategy=='fifo':
-            result += self._quants_get_fifo(cr, uid, location, product, qty, domain, context=context)
-        elif removal_strategy=='lifo':
-            result += self._quants_get_lifo(cr, uid, location, product, qty, domain, context=context)
-        else:
-            raise osv.except_osv(_('Error!'),_('Removal strategy %s not implemented.' % (removal_strategy,)))
+        if location:
+            removal_strategy = self.pool.get('stock.location').get_removal_strategy(cr, uid, location, product, context=context) or 'fifo'
+            if removal_strategy=='fifo':
+                result += self._quants_get_fifo(cr, uid, location, product, qty, domain, context=context)
+            elif removal_strategy=='lifo':
+                result += self._quants_get_lifo(cr, uid, location, product, qty, domain, context=context)
+            else:
+                raise osv.except_osv(_('Error!'),_('Removal strategy %s not implemented.' % (removal_strategy,)))
+
         return result
 
 
@@ -296,8 +298,11 @@ class stock_quant(osv.osv):
                 'cost': 0.0,
             }, context=context)
 
-            quants2 = self.quants_get(cr, uid, False, quant.product_id, quant_neg.qty, [('propagated_from_id','=',quant_neg.id)], context=context)
+            
+            #TODO: In case of negative quants no removal strategy is applied -> actually removal strategy should be reversed? OR just by in_date?
+            quants2 = self._quants_get_order(cr, uid, False, quant.product_id, -quant_neg.qty, domain=[('propagated_from_id','=',quant_neg.id)], orderby='in_date', context=None)
             for qu2, qt2 in quants2:
+                #TODO  history ids on quant!
                 if not qu2: raise 'Error: negative stock linked to nothing'
                 self._quant_split(cr, uid, qu2, qt2, context=context)
                 self.write(cr, uid, [qu2.id], {
@@ -335,6 +340,8 @@ class stock_quant(osv.osv):
             offset += 10
         return res
 
+
+
     def _quants_get_fifo(self, cr, uid, location, product, quantity, domain=[], context=None):
         return self._quants_get_order(cr, uid, location, product, quantity,
             domain, 'in_date', context=context)
@@ -715,13 +722,20 @@ class stock_picking(osv.osv):
         """ Confirms picking.
         @return: True
         """
+        proc_group = self.pool.get("procurement.group")
         self.write(cr, uid, ids, {'state': 'confirmed'})
         pickings = self.browse(cr, uid, ids, context=context)
         todo = []
         for picking in pickings:
+            #If no procurement group, create one
+            new_proc = False
+            if not picking.group_id:
+                new_proc = proc_group.create(cr, uid, {'name': picking.name}, context=context)
             for r in picking.move_lines:
                 if r.state == 'draft':
                     todo.append(r.id)
+                if not r.group_id and new_proc:
+                    self.pool.get("stock.move").write(cr, uid, [r.id], {'group_id': new_proc}, context=context)
         if len(todo):
             self.pool.get('stock.move').action_confirm(cr, uid, todo, context=context)
         return True
@@ -1799,6 +1813,7 @@ class stock_move(osv.osv):
                 'product_uos': (move.product_uos and move.product_uos.id) or move.product_uom.id,
                 'location_id': move.location_id.id,
                 'move_id': move.id,
+                'group_id': move.group_id and move.group_id.id or False, 
             })
 
     # Check that we do not modify a stock.move which is done
@@ -2007,6 +2022,7 @@ class stock_move(osv.osv):
             if not move.picking_id:
                 # TODO: Put the move in the right picking according to group_id -> should be more elaborated (draft is nok) and picking should be confirmed
                 pick_obj = self.pool.get("stock.picking")
+                print "Group ID", move.group_id.id
                 picks = pick_obj.search(cr, uid, [('group_id', '=', move.group_id.id), ('location_id', '=', move.location_id.id), 
                                           ('location_dest_id', '=', move.location_dest_id.id), ('state', 'in', ['confirmed', 'waiting', 'draft'])], context=context)
                 if picks:
diff --git a/addons/stock/stock_fifo_lifo.py b/addons/stock/stock_fifo_lifo.py
deleted file mode 100644 (file)
index a635f39..0000000
+++ /dev/null
@@ -1,218 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-#    OpenERP, Open Source Management Solution
-#    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
-#
-#    This program is free software: you can redistribute it and/or modify
-#    it under the terms of the GNU Affero General Public License as
-#    published by the Free Software Foundation, either version 3 of the
-#    License, or (at your option) any later version.
-#
-#    This program is distributed in the hope that it will be useful,
-#    but WITHOUT ANY WARRANTY; without even the implied warranty of
-#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#    GNU Affero General Public License for more details.
-#
-#    You should have received a copy of the GNU Affero General Public License
-#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
-#
-##############################################################################
-
-from openerp import tools
-from openerp.osv import osv, fields
-
-
-class product_product (osv.osv):
-    _name = "product.product"
-    _inherit = "product.product"
-
-    def _compute_and_set_avg_price(self, cr, uid, prod, company_id, context=None):
-        """
-        This method is called when we modify the cost method of a product 'prod' from something to 'average'. At that time, we
-        will compute and set the current average price on the product.
-
-        :param prod: browse_record(product.product)
-        :param company_id: ID of the company to consider
-        """
-        move_obj = self.pool.get("stock.move")
-        uom_obj = self.pool.get("product.uom")
-        qty = 0.0
-        total_price = 0.0
-        #search all outgoing stock moves and count the total cost and total quantity
-        mov_ids = move_obj.search(cr, uid, [('product_id', '=', prod.id), ('company_id', '=', company_id), ('state', '=', 'done'), ('location_dest_id.usage', '=', 'internal'), ('location_id.usage', '!=', 'internal')], context=context)
-        for move in move_obj.browse(cr, uid, mov_ids, context=context):
-            total_price += move.product_qty * move.price_unit
-            qty += uom_obj._compute_qty(cr, uid, move.product_uom.id, move.product_qty, prod.uom_id.id, round=False)
-        if qty > 0.0:
-            self.write(cr, uid, [prod.id], {'standard_price': total_price / qty}, context=context)
-
-    def _update_moves_set_avg_price(self, cr, uid, prod, company_id, context=None):
-        """
-        This method is called when we modify the cost method of a product 'prod' from average to something else. At that time, we
-        will set the current average price on all the stock moves with a positive remaining quantity.
-
-        :param prod: browse_record(product.product)
-        :param company_id: ID of the company to consider
-        """
-        move_obj = self.pool.get("stock.move")
-        uom_obj = self.pool.get("product.uom")
-        mov_ids = move_obj.search(cr, uid, [('product_id', '=', prod.id), ('company_id', '=', company_id), ('state', '=', 'done'), ('qty_remaining', '>', 0.0)], context=context)
-        for move in move_obj.browse(cr, uid, mov_ids, context=context):
-            #convert the average price of the product into the stock move uom
-            new_price = uom_obj._compute_price(cr, uid, move.product_uom.id, prod.standard_price, prod.uom_id.id)
-            move_obj.write(cr, uid, [move.id], {'price_unit': new_price}, context=context)
-
-    def write(self, cr, uid, ids, vals, context=None):
-        if context is None:
-            context = {}
-        #If we changed the cost method of the product, we may need to do some operation on stock moves or on the product itself
-        if "cost_method" in vals:
-            company_id = context.get('force_company', self.pool.get("res.users").browse(cr, uid, uid, context=context).company_id.id)
-            for prod in self.browse(cr, uid, ids, context=context):
-                if prod.cost_method == 'average' and vals['cost_method'] != 'average':
-                    #If we are changing the cost_method from 'average' to something else, we need to store the current average price
-                    #on all the done stock move that have a remaining quantity to be matched (mostly incomming stock moves but it may
-                    #include OUT stock moves as well if the stock went into negative) because their further valuation needs to be done
-                    #using this price.
-                    self._update_moves_set_avg_price(cr, uid, prod, company_id, context=context)
-                elif vals["cost_method"] == 'average' and prod.cost_method != 'average':
-                    #If we are changing the cost_method from anything to 'average', we need to compute the current average price 
-                    #and set it on the product as standard_price.
-                    self._compute_and_set_avg_price(cr, uid, prod, company_id, context=context)
-        return super(product_product, self).write(cr, uid, ids, vals, context=context)
-    
-    def get_stock_matchings_fifolifo(self, cr, uid, ids, qty, fifo, product_uom_id=False, currency_id=False, context=None):
-        # TODO: currency conversions could be omitted
-        '''
-        This method returns a list of tuples with quantities from stock in moves
-        These are the quantities from the in moves that would go out theoretically according to the fifo or lifo method
-        (move_in_id, qty in uom of move out, price (converted to move out), qty in uom of move in)
-        This should be called for only one product at a time
-        UoMs and currencies from the corresponding moves are converted towards that given in the params
-        force_company should be used in the context
-        '''
-        assert len(ids) == 1, 'Only the fifolifo stock matchings of one product can be calculated at a time.'
-        if context is None:
-            context = {}
-        uom_obj = self.pool.get('product.uom')
-        move_obj = self.pool.get('stock.move')
-        currency_obj = self.pool.get('res.currency')
-        
-        product = self.browse(cr, uid, ids, context=context)[0]
-        if not product_uom_id: 
-            product_uom_id = product.uom_id.id
-        company_id = context.get('force_company', product.company_id.id)
-
-        if not currency_id:
-            currency_id = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id.id
-        if fifo:
-            order = 'date, id'
-        else: 
-            order = 'date desc, id desc' #id also for yml tests
-        move_in_ids = move_obj.search(cr, uid, [('company_id', '=', company_id), 
-                                                ('qty_remaining', '>', 0.0), 
-                                                ('state', '=', 'done'), 
-                                                ('location_id.usage', '!=', 'internal'), 
-                                                ('location_dest_id.usage', '=', 'internal'), 
-                                                ('product_id', '=', product.id)], 
-                                       order = order, context=context)
-        tuples = []
-        qty_to_go = qty
-        for move in move_obj.browse(cr, uid, move_in_ids, context=context):
-            #Convert to UoM of product each time
-            uom_from = move.product_uom.id
-            qty_from = move.qty_remaining
-            product_qty = uom_obj._compute_qty(cr, uid, uom_from, qty_from, product_uom_id, round=False)
-            #Convert currency from in move currency id to out move currency
-            if move.price_currency_id and (move.price_currency_id.id != currency_id):
-                new_price = currency_obj.compute(cr, uid, move.price_currency_id.id, currency_id, 
-                                                 move.price_unit, round=False)
-            else:
-                new_price = move.price_unit
-            new_price = uom_obj._compute_price(cr, uid, uom_from, new_price, product_uom_id)
-            if qty_to_go - product_qty >= 0: 
-                tuples.append((move.id, product_qty, new_price, qty_from),)
-                qty_to_go -= product_qty
-            else:
-                tuples.append((move.id, qty_to_go, new_price, qty_from * qty_to_go / product_qty),)
-                break
-        return tuples
-
-
-class stock_move(osv.osv):
-    _inherit = 'stock.move'
-    
-    def _get_moves_from_matchings(self, cr, uid, ids, context=None):
-        res = set()
-        for match in self.browse(cr, uid, ids, context=context):
-            res.add(match.move_out_id.id)
-            res.add(match.move_in_id.id)
-        return list(res)
-
-    def _get_qty_remaining (self, cr, uid, ids, field_names, arg, context=None):
-        '''
-        This function calculates how much of the stock move that still needs to be matched
-        '''
-        match_obj = self.pool.get("stock.move.matching")
-        uom_obj = self.pool.get("product.uom")
-        res = {}
-        for move in self.browse(cr, uid, ids, context=context):
-            qty = move.product_qty
-            if move.location_id.usage != 'internal' and move.location_dest_id.usage == 'internal':
-                # for incomming moves, the remaining quantity is the quantity what hasn't been matched yet with outgoing moves
-                matches = match_obj.search(cr, uid, [('move_in_id', '=', move.id)], context=context)
-                for match in match_obj.browse(cr, uid, matches, context=context):
-                    qty -= uom_obj._compute_qty(cr, uid, match.move_out_id.product_uom.id, match.qty, move.product_uom.id, round=False)
-            elif move.location_id.usage == 'internal' and move.location_dest_id.usage != 'internal':
-                # for outgoing moves, we need to compute the remaining quantity to manage the negative stocks.
-                # We do that in the same way that for incomming moves.
-                matches = match_obj.search(cr, uid, [('move_out_id', '=', move.id)], context=context)
-                for match in match_obj.browse(cr, uid, matches, context=context):
-                    # we don't need to call uom_obj.compute() as the qty on the matching is already in the uom of the out move
-                    qty -= match.qty
-            else:
-                # we don't use remaining quantity on internal moves (no matching are created)
-                qty = 0
-            res[move.id] = qty
-        return res
-
-    _columns = {'qty_remaining': fields.function(_get_qty_remaining, type="float", string="Remaining quantity to be matched", 
-                                                 store = {'stock.move.matching': (_get_moves_from_matchings, ['qty', 'move_in_id', 'move_out_id'], 10),
-                                                          'stock.move':  (lambda self, cr, uid, ids, ctx: ids, ['product_qty', 'product_uom', 'location_id', 'location_dest_id'], 10)}),
-                'matching_ids_in': fields.one2many('stock.move.matching', 'move_in_id'),
-                'matching_ids_out':fields.one2many('stock.move.matching', 'move_out_id'),
-    }
-
-
-class stock_move_matching(osv.osv):
-    _name = "stock.move.matching"
-    _description = "Stock move matchings"
-    
-    def _get_unit_price_out (self, cr, uid, ids, field_names, arg, context=None):
-        res = {}
-        uom_obj = self.pool.get("product.uom")
-        for match in self.browse(cr, uid, ids, context=context):
-            res[match.id] = uom_obj._compute_price(cr, uid, match.move_in_id.product_uom.id, match.price_unit, match.move_out_id.product_uom.id)
-        return res
-
-    def _get_matches(self, cr, uid, ids, context=None):
-        move_in_ids = []
-        #TOCHECK : self == move_obj i think
-        move_obj = self.pool.get("stock.move")
-        for move in move_obj.browse(cr, uid, ids, context=context):
-            if move.location_id.usage != 'internal' and move.location_dest_id.usage == 'internal':
-                move_in_ids.append(move.id)
-        return self.pool.get("stock.move.matching").search(cr, uid, [('move_in_id', 'in', move_in_ids)], context=context)
-
-    _columns = {
-        'move_in_id': fields.many2one('stock.move', 'Stock move in', required=True),
-        'move_out_id': fields.many2one('stock.move', 'Stock move out', required=True),
-        'qty': fields.float('Quantity in UoM of out', required=True), 
-        'price_unit':fields.related('move_in_id', 'price_unit', string="Unit price", type="float"),
-        'price_unit_out': fields.function(_get_unit_price_out, type="float", string="Price in UoM of out move", 
-                                                 store = {'stock.move': (_get_matches, ['price_unit', 'product_uom'], 10), 
-                                                          'stock.move.matching': (lambda self, cr, uid, ids, ctx: ids, ['move_in_id', 'qty', 'move_out_id'], 10)},), 
-    }
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
index c1de624..bd479a1 100644 (file)
                     <group>
                         <group name="main_grp" string="Details">
                             <field name="product_id" on_change="onchange_product_id(product_id,location_id,location_dest_id, False)"/>
+                            <field name="procure_method"/>
+                            <field name="group_id"/>
                             <label for="product_uom_qty"/>
                             <div>
                                 <field name="product_uom_qty"
index 46558c7..7b896ed 100644 (file)
@@ -137,7 +137,7 @@ class procurement_order(osv.osv):
             date = procurement.move_dest_id.date
         else:
             date = procurement.date_planned
-        procure_method = procurement.rule_id and procurement.rule_id.procure_method or 'make_to_stock',
+        procure_method = procurement.rule_id and procurement.rule_id.procure_method or 'make_to_stock'
         newdate = (datetime.strptime(date, '%Y-%m-%d %H:%M:%S') - relativedelta(days=procurement.rule_id.delay or 0)).strftime('%Y-%m-%d %H:%M:%S')
         d.update({
             'date': newdate,
@@ -282,6 +282,6 @@ class stock_location(osv.osv):
         ], context=context)
         if result:
             return pr.browse(cr, uid, result[0], context=context)
-        return super(stock_location, self).get_removal_strategy(cr, uid, id, product, context=context)
+        return super(stock_location, self).get_removal_strategy(cr, uid, location, product, context=context)
 
 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: