From c7880886176117fe66d630e26f995e8d9d164aca Mon Sep 17 00:00:00 2001 From: "Quentin (OpenERP)" Date: Tue, 8 Apr 2014 18:01:44 +0200 Subject: [PATCH 1/6] [REF] stock, inventory: post moves at the inventory closing date + removal of the constraints because it's not needed anymore now bzr revid: qdp-launchpad@openerp.com-20140408160144-nb5b7rjxygbblud3 --- addons/stock/stock.py | 88 +++++++++------------ addons/stock_account/stock_account.py | 21 ++++- addons/stock_account/stock_account_view.xml | 11 ++- 3 files changed, 64 insertions(+), 56 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 8c667b17e3e..64d646a872f 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -587,32 +587,6 @@ class stock_quant(osv.osv): raise osv.except_osv(_('Error'), _('You cannot move to a location of type view %s.') % (location.name)) return True - def _check_open_inventory_location(self, cr, uid, ids, context=None): - """ Check if there is an inventory running for the locations implied - """ - inventory_obj = self.pool.get('stock.inventory') - for quant in self.browse(cr, uid, ids, context=context): - loc = quant.location_id - parent_location_ids = [] - while loc: - parent_location_ids.append(loc.id) - loc = loc.location_id - domain = [('state', '=', 'confirm'), ('location_id', 'in', parent_location_ids), '|', ('product_id', '=', False), ('product_id', '=', quant.product_id.id)] - domain += ['|', ('lot_id', '=', False), ('lot_id', '=', quant.lot_id.id)] - domain += ['|', ('partner_id', '=', False), ('partner_id', '=', quant.owner_id.id)] - domain += ['|', ('package_id', '=', False), ('package_id', '=', quant.package_id.id)] - inventory_ids = inventory_obj.search(cr, uid, domain, context=context) - if inventory_ids: - inventory = inventory_obj.browse(cr, uid, inventory_ids[0], context=context) - raise osv.except_osv(_('Error! Location on inventory'), - _('There exists an inventory conflicting with your operation :\n%s') % inventory.name) - return True - - _constraints = [ - (_check_open_inventory_location, - "A Physical Inventory is being conducted at this location", ['location_id']), - ] - def write(self, cr, uid, ids, vals, context=None): #check the inventory constraint before the write if isinstance(ids, (int, long)): @@ -2253,8 +2227,7 @@ class stock_move(osv.osv): # Check the packages have been placed in the correct locations self._check_package_from_moves(cr, uid, ids, context=context) # Apply on picking - done_date = context.get('force_date', time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)) - self.write(cr, uid, ids, {'state': 'done', 'date': done_date}, context=context) + self.write(cr, uid, ids, {'state': 'done', 'date': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)}, context=context) self.pool.get('procurement.order').check(cr, uid, procurement_ids, context=context) #check picking state to set the date_done is needed done_picking = [] @@ -2427,6 +2400,7 @@ class stock_inventory(osv.osv): 'name': fields.char('Inventory Reference', size=64, required=True, readonly=True, states={'draft': [('readonly', False)]}, help="Inventory Name."), 'date': fields.datetime('Inventory Date', required=True, readonly=True, states={'draft': [('readonly', False)]}, help="The date that will be used for the validation date of the stock move related to this inventory (and for the valuation accounting entries, if any)"), 'line_ids': fields.one2many('stock.inventory.line', 'inventory_id', 'Inventories', readonly=False, states={'done': [('readonly', True)]}, help="Inventory Lines."), + 'theoretical_line_ids': fields.one2many('stock.theoretical.inventory.line', 'inventory_id', 'Theoretical Inventory Lines', readonly=False, states={'done': [('readonly', True)]}), 'move_ids': fields.one2many('stock.move', 'inventory_id', 'Created Moves', help="Inventory Moves.", states={'done': [('readonly', True)]}), 'state': fields.selection(INVENTORY_STATE_SELECTION, 'Status', readonly=True, select=True), 'company_id': fields.many2one('res.company', 'Company', required=True, select=True, readonly=True, states={'draft': [('readonly', False)]}), @@ -2479,25 +2453,23 @@ class stock_inventory(osv.osv): """ Finish the inventory @return: True """ - if context is None: - context = {} - move_obj = self.pool.get('stock.move') for inv in self.browse(cr, uid, ids, context=context): for inventory_line in inv.line_ids: if inventory_line.product_qty < 0 and inventory_line.product_qty != inventory_line.th_qty: raise osv.except_osv(_('Warning'), _('You cannot set a negative product quantity in an inventory line:\n\t%s - qty: %s' % (inventory_line.product_id.name, inventory_line.product_qty))) - if not inv.move_ids: - self.action_check(cr, uid, [inv.id], context=context) + self.action_check(cr, uid, [inv.id], context=context) inv.refresh() self.write(cr, uid, [inv.id], {'state': 'done'}, context=context) - #The inventory is posted as a single step which means quants cannot be moved from an internal location to another using an inventory - #as they will be moved to inventory loss, and other quants will be created to the encoded quant location. This is a normal behavior - #as quants cannot be reuse from inventory location (users can still manually move the products before/after the inventory if they want). - ctx = context.copy() - ctx['force_date'] = inv.date - move_obj.action_done(cr, uid, [x.id for x in inv.move_ids], context=ctx) + self.post_inventory(cr, uid, inv, context=context) return True + def post_inventory(self, cr, uid, inv, context=None): + #The inventory is posted as a single step which means quants cannot be moved from an internal location to another using an inventory + #as they will be moved to inventory loss, and other quants will be created to the encoded quant location. This is a normal behavior + #as quants cannot be reuse from inventory location (users can still manually move the products before/after the inventory if they want). + move_obj = self.pool.get('stock.move') + move_obj.action_done(cr, uid, [x.id for x in inv.move_ids], context=context) + def _create_stock_move(self, cr, uid, inventory, todo_line, context=None): stock_move_obj = self.pool.get('stock.move') product_obj = self.pool.get('product.product') @@ -2537,7 +2509,7 @@ class stock_inventory(osv.osv): move_ids = [move.id for move in inventory.move_ids] stock_move_obj.unlink(cr, uid, move_ids, context=context) #compute what should be in the inventory lines - theorical_lines = self._get_inventory_lines(cr, uid, inventory, context=context) + theorical_lines = self._get_th_inventory_lines(cr, uid, inventory, context=context) for line in inventory.line_ids: #compare the inventory lines to the theorical ones and store the diff in theorical_lines inventory_line_obj._resolve_inventory_line(cr, uid, line, theorical_lines, context=context) @@ -2558,31 +2530,23 @@ class stock_inventory(osv.osv): def action_cancel_inventory(self, cr, uid, ids, context=None): self.action_cancel_draft(cr, uid, ids, context=context) - def check_inventory_date(self, cr, uid, inventory, context=None): - domain = ['|', ('location_id', 'child_of', [inventory.location_id.id]), ('location_dest_id', 'child_of', [inventory.location_id.id]), ('date', '>', inventory.date), '|', ('state', 'in', ['assigned', 'done']), '&', ('state', 'in', ['confirmed', 'waiting']), ('partially_available', '=', True)] - if inventory.product_id: - domain += [('product_id', '=', inventory.product_id.id)] - return self.pool.get('stock.move').search(cr, uid, domain, context=context) - def prepare_inventory(self, cr, uid, ids, context=None): inventory_line_obj = self.pool.get('stock.inventory.line') + th_inventory_line_obj = self.pool.get('stock.theoretical.inventory.line') for inventory in self.browse(cr, uid, ids, context=context): #check inventory start date is allowed if inventory.date > time.strftime(DEFAULT_SERVER_DATETIME_FORMAT): raise osv.except_osv(_('Error!'), _('It\'s impossible to confirm an inventory in the future. Please change the inventory date to proceed further.')) - conflicting_move_ids = self.check_inventory_date(cr, uid, inventory, context=context) - error_message = "" - for move in self.pool.get('stock.move').browse(cr, uid, conflicting_move_ids, context=context): - error_message += _("\n * Date: %s - %s %s - From: %s To: %s State: %s") % (move.date, move.product_uom_qty, move.product_uom.name, move.location_id.name, move.location_dest_id.name, move.state) - if conflicting_move_ids: - raise osv.except_osv(_('Error!'), _('Stock moves exist made/scheduled after the inventory date which are conflicting with its settings. Please unreserve them or change the inventory date to proceed further.\n\n%s') % (error_message)) #clean the existing inventory lines before redoing an inventory proposal line_ids = [line.id for line in inventory.line_ids] inventory_line_obj.unlink(cr, uid, line_ids, context=context) + th_line_ids = [line.id for line in inventory.theoretical_line_ids] + th_inventory_line_obj.unlink(cr, uid, th_line_ids, context=context) #compute the inventory lines and create them vals = self._get_inventory_lines(cr, uid, inventory, context=context) for product_line in vals: inventory_line_obj.create(cr, uid, product_line, context=context) + th_inventory_line_obj.create(cr, uid, product_line, context=context) return self.write(cr, uid, ids, {'state': 'confirm'}) def _get_inventory_lines(self, cr, uid, inventory, context=None): @@ -2622,6 +2586,20 @@ class stock_inventory(osv.osv): vals.append(product_line) return vals + def _get_th_inventory_lines(self, cr, uid, inventory, context=None): + th_vals = [] + for th_line in inventory.theoretical_inventory_lines: + th_vals.append({ + 'product_id': th_line.product_id.id, + 'product_qty': th_line.product_qty, + 'location_id': th_line.location_id.id, + 'prod_lot_id': th_line.prod_lot_id.id, + 'package_id': th_line.package_id.id, + 'partner_id': th_line.partner_id.id + }) + return th_vals + + class stock_inventory_line(osv.osv): _name = "stock.inventory.line" _description = "Inventory Line" @@ -2716,6 +2694,12 @@ class stock_inventory_line(osv.osv): return {'value': {'th_qty': th_qty, 'product_uom_id': uom or obj_product.uom_id.id}} +class stock_theoretical_inventory_line(osv.osv): + """ Simple copy of the inventory line table used to keep track of the theoretical inventory lines that should be recorded in inventories. It's filled at the time an inventory is started and checked and the time it's done to see the difference and apply stock move accordingly. We could have used another relation between inventory and inventory lines but having a second table is easier to make sure we don't introduce any side effect """ + _inherit = "stock.inventory.line" + _name = "stock.theoretical.inventory.line" + + #---------------------------------------------------------- # Stock Warehouse #---------------------------------------------------------- diff --git a/addons/stock_account/stock_account.py b/addons/stock_account/stock_account.py index 9bbad8e52b4..d83f800305c 100644 --- a/addons/stock_account/stock_account.py +++ b/addons/stock_account/stock_account.py @@ -19,14 +19,28 @@ # ############################################################################## -import time - from openerp.osv import fields, osv from openerp.tools.translate import _ from openerp import SUPERUSER_ID import logging _logger = logging.getLogger(__name__) + +class stock_inventory(osv.osv): + _inherit = "stock.inventory" + _columns = { + 'period_id': fields.many2one('account.period', 'Force Valuation Period', help="Choose the accounting period where you want to value the stock moves created by the inventory instead of the default one (chosen by the inventory end date)"), + } + + def post_inventory(self, cr, uid, inv, context=None): + if context is None: + context = {} + ctx = context.copy() + if inv.period_id: + ctx['force_period'] = inv.period_id.id + return super(stock_inventory, self).post_inventory(cr, uid, inv, context=ctx) + + #---------------------------------------------------------- # Stock Location #---------------------------------------------------------- @@ -227,9 +241,10 @@ class stock_quant(osv.osv): move_obj = self.pool.get('account.move') for cost, qty in quant_cost_qty.items(): move_lines = self._prepare_account_move_line(cr, uid, move, qty, cost, credit_account_id, debit_account_id, context=context) + period_id = context.get('force_period', self.pool.get('account.period').find(cr, uid, move.date, context=context)[0]) return move_obj.create(cr, uid, {'journal_id': journal_id, 'line_id': move_lines, - 'period_id': self.pool.get('account.period').find(cr, uid, move.date, context=context)[0], + 'period_id': period_id, 'date': move.date, 'ref': move.picking_id and move.picking_id.name}, context=context) diff --git a/addons/stock_account/stock_account_view.xml b/addons/stock_account/stock_account_view.xml index 14a09d9e3ce..64b6a89cea9 100644 --- a/addons/stock_account/stock_account_view.xml +++ b/addons/stock_account/stock_account_view.xml @@ -1,6 +1,16 @@ + + stock.inventory.form.inherit + stock.inventory + + + + + + + stock.location.form.inherit @@ -16,7 +26,6 @@ - stock.picking.form.inherit stock.picking From 4908a107d1144899a18b2b8e84f411c731dcf5cb Mon Sep 17 00:00:00 2001 From: "Quentin (OpenERP)" Date: Wed, 9 Apr 2014 11:30:25 +0200 Subject: [PATCH 2/6] [FIX] stock: inventory using theoretical_line_ids bzr revid: qdp-launchpad@openerp.com-20140409093025-kgyyo2tnrucon55a --- addons/stock/stock.py | 17 ++++++++++++++--- addons/stock/stock_demo.xml | 3 +++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 64d646a872f..c94059309c4 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -2588,7 +2588,7 @@ class stock_inventory(osv.osv): def _get_th_inventory_lines(self, cr, uid, inventory, context=None): th_vals = [] - for th_line in inventory.theoretical_inventory_lines: + for th_line in inventory.theoretical_line_ids: th_vals.append({ 'product_id': th_line.product_id.id, 'product_qty': th_line.product_qty, @@ -2603,7 +2603,6 @@ class stock_inventory(osv.osv): class stock_inventory_line(osv.osv): _name = "stock.inventory.line" _description = "Inventory Line" - _rec_name = "inventory_id" _order = "inventory_id, location_name, product_code, product_name, prodlot_name" def _get_product_name_change(self, cr, uid, ids, context=None): @@ -2696,8 +2695,20 @@ class stock_inventory_line(osv.osv): class stock_theoretical_inventory_line(osv.osv): """ Simple copy of the inventory line table used to keep track of the theoretical inventory lines that should be recorded in inventories. It's filled at the time an inventory is started and checked and the time it's done to see the difference and apply stock move accordingly. We could have used another relation between inventory and inventory lines but having a second table is easier to make sure we don't introduce any side effect """ - _inherit = "stock.inventory.line" _name = "stock.theoretical.inventory.line" + _columns = { + 'inventory_id': fields.many2one('stock.inventory', 'Inventory', ondelete='cascade', select=True), + 'location_id': fields.many2one('stock.location', 'Location', required=True, select=True), + 'product_id': fields.many2one('product.product', 'Product', required=True, select=True), + 'package_id': fields.many2one('stock.quant.package', 'Pack', select=True), + 'product_uom_id': fields.many2one('product.uom', 'Product Unit of Measure', required=True), + 'product_qty': fields.float('Checked Quantity', digits_compute=dp.get_precision('Product Unit of Measure')), + 'company_id': fields.related('inventory_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, select=True, readonly=True), + 'prod_lot_id': fields.many2one('stock.production.lot', 'Serial Number', domain="[('product_id','=',product_id)]"), + 'state': fields.related('inventory_id', 'state', type='char', string='Status', readonly=True), + 'th_qty': fields.float('Theoretical Quantity', readonly=True), + 'partner_id': fields.many2one('res.partner', 'Owner'), + } #---------------------------------------------------------- diff --git a/addons/stock/stock_demo.xml b/addons/stock/stock_demo.xml index 64148c3485e..e7a5f2fd664 100644 --- a/addons/stock/stock_demo.xml +++ b/addons/stock/stock_demo.xml @@ -151,6 +151,9 @@ + + + From 7c810ff6fe0a0a70bbb9cbf9f113e04772513d20 Mon Sep 17 00:00:00 2001 From: "Quentin (OpenERP)" Date: Wed, 9 Apr 2014 15:08:59 +0200 Subject: [PATCH 3/6] [FIX] stock, inventory management bzr revid: qdp-launchpad@openerp.com-20140409130859-0mla46wyl6ubecyb --- addons/stock/stock.py | 9 +-------- addons/stock/stock_demo.xml | 3 --- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index c94059309c4..d5f63c004dd 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -587,14 +587,6 @@ class stock_quant(osv.osv): raise osv.except_osv(_('Error'), _('You cannot move to a location of type view %s.') % (location.name)) return True - def write(self, cr, uid, ids, vals, context=None): - #check the inventory constraint before the write - if isinstance(ids, (int, long)): - ids = [ids] - if 'owner_id' in vals or 'lot_id' in vals or 'package_id' in vals or 'location_id' in vals or 'product_id' in vals: - self._check_open_inventory_location(cr, uid, ids, context=context) - return super(stock_quant, self).write(cr, uid, ids, vals, context=context) - #---------------------------------------------------------- # Stock Picking @@ -2591,6 +2583,7 @@ class stock_inventory(osv.osv): for th_line in inventory.theoretical_line_ids: th_vals.append({ 'product_id': th_line.product_id.id, + 'product_uom_id': th_line.product_uom_id.id, 'product_qty': th_line.product_qty, 'location_id': th_line.location_id.id, 'prod_lot_id': th_line.prod_lot_id.id, diff --git a/addons/stock/stock_demo.xml b/addons/stock/stock_demo.xml index e7a5f2fd664..64148c3485e 100644 --- a/addons/stock/stock_demo.xml +++ b/addons/stock/stock_demo.xml @@ -151,9 +151,6 @@ - - - From 347d527c71e54ec66297b01da80652bccb080b79 Mon Sep 17 00:00:00 2001 From: "Quentin (OpenERP)" Date: Wed, 9 Apr 2014 16:07:57 +0200 Subject: [PATCH 4/6] [FIX] stock, inventory: don't care about the time when the 'start inventory' button is pressed, but always check the inventory date to know the stock level bzr revid: qdp-launchpad@openerp.com-20140409140757-yau6qs2423ickavx --- addons/stock/stock.py | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index d5f63c004dd..691f80f58c6 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -2546,7 +2546,7 @@ class stock_inventory(osv.osv): product_obj = self.pool.get('product.product') location_ids = location_obj.search(cr, uid, [('id', 'child_of', [inventory.location_id.id])], context=context) domain = ' location_id in %s' - args = (tuple(location_ids),) + args = (inventory.date, inventory.date, tuple(location_ids),) if inventory.partner_id: domain += ' and owner_id = %s' args += (inventory.partner_id.id,) @@ -2559,9 +2559,39 @@ class stock_inventory(osv.osv): if inventory.package_id: domain += ' and package_id = %s' args += (inventory.package_id.id,) + cr.execute(''' SELECT product_id, sum(qty) as product_qty, location_id, lot_id as prod_lot_id, package_id, owner_id as partner_id - FROM stock_quant WHERE''' + domain + ''' + FROM ((SELECT + stock_move.id::text || '-' || quant.id::text AS id, + stock_move.location_dest_id AS location_id, + stock_move.product_id, + quant.qty AS qty, + quant.owner_id, + quant.package_id, + quant.lot_id, + stock_move.date + FROM + stock_quant as quant, stock_quant_move_rel, stock_move + LEFT JOIN stock_location location ON (stock_move.location_dest_id = location.id) + WHERE stock_move.state = 'done' AND location.usage = 'internal' AND stock_quant_move_rel.quant_id = quant.id + AND stock_quant_move_rel.move_id = stock_move.id AND stock_move.date < %s + ) UNION + (SELECT + '-' || stock_move.id::text || '-' || quant.id::text AS id, + stock_move.location_id AS location_id, + stock_move.product_id, + - quant.qty AS qty, + quant.owner_id, + quant.package_id, + quant.lot_id, + stock_move.date + FROM + stock_quant as quant, stock_quant_move_rel, stock_move + LEFT JOIN stock_location location ON (stock_move.location_id = location.id) + WHERE stock_move.state = 'done' AND location.usage = 'internal' AND stock_quant_move_rel.quant_id = quant.id + AND stock_quant_move_rel.move_id = stock_move.id AND stock_move.date < %s + )) AS subquery WHERE''' + domain + ''' GROUP BY product_id, location_id, lot_id, package_id, partner_id ''', args) vals = [] From 9f19f13b5cb7d1f7d6465014c4c37f48826aa64b Mon Sep 17 00:00:00 2001 From: "Quentin (OpenERP)" Date: Thu, 10 Apr 2014 10:15:30 +0200 Subject: [PATCH 5/6] [REF] stock: inventory refactoring. Keep it simple and easy bzr revid: qdp-launchpad@openerp.com-20140410081530-mzxr2laqewqmgeyb --- addons/stock/stock.py | 140 +++++++++--------------------------- addons/stock/stock_view.xml | 3 + 2 files changed, 38 insertions(+), 105 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 691f80f58c6..96ec2c1d4fe 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -2390,9 +2390,8 @@ class stock_inventory(osv.osv): _columns = { 'name': fields.char('Inventory Reference', size=64, required=True, readonly=True, states={'draft': [('readonly', False)]}, help="Inventory Name."), - 'date': fields.datetime('Inventory Date', required=True, readonly=True, states={'draft': [('readonly', False)]}, help="The date that will be used for the validation date of the stock move related to this inventory (and for the valuation accounting entries, if any)"), + 'date': fields.datetime('Inventory Date', required=True, readonly=True, help="The date that will be used for the stock level check of the products and the validation of the stock move related to this inventory."), 'line_ids': fields.one2many('stock.inventory.line', 'inventory_id', 'Inventories', readonly=False, states={'done': [('readonly', True)]}, help="Inventory Lines."), - 'theoretical_line_ids': fields.one2many('stock.theoretical.inventory.line', 'inventory_id', 'Theoretical Inventory Lines', readonly=False, states={'done': [('readonly', True)]}), 'move_ids': fields.one2many('stock.move', 'inventory_id', 'Created Moves', help="Inventory Moves.", states={'done': [('readonly', True)]}), 'state': fields.selection(INVENTORY_STATE_SELECTION, 'Status', readonly=True, select=True), 'company_id': fields.many2one('res.company', 'Company', required=True, select=True, readonly=True, states={'draft': [('readonly', False)]}), @@ -2500,15 +2499,9 @@ class stock_inventory(osv.osv): #first remove the existing stock moves linked to this inventory move_ids = [move.id for move in inventory.move_ids] stock_move_obj.unlink(cr, uid, move_ids, context=context) - #compute what should be in the inventory lines - theorical_lines = self._get_th_inventory_lines(cr, uid, inventory, context=context) for line in inventory.line_ids: - #compare the inventory lines to the theorical ones and store the diff in theorical_lines - inventory_line_obj._resolve_inventory_line(cr, uid, line, theorical_lines, context=context) - #each theorical_lines where product_qty is not 0 is a difference for which we need to create a stock move - for todo_line in theorical_lines: - if todo_line['product_qty'] != 0: - self._create_stock_move(cr, uid, inventory, todo_line, context=context) + #compare the checked quantities on inventory lines to the theorical one + inventory_line_obj._resolve_inventory_line(cr, uid, line, context=context) def action_cancel_draft(self, cr, uid, ids, context=None): """ Cancels the stock move and change inventory state to draft. @@ -2524,29 +2517,22 @@ class stock_inventory(osv.osv): def prepare_inventory(self, cr, uid, ids, context=None): inventory_line_obj = self.pool.get('stock.inventory.line') - th_inventory_line_obj = self.pool.get('stock.theoretical.inventory.line') for inventory in self.browse(cr, uid, ids, context=context): - #check inventory start date is allowed - if inventory.date > time.strftime(DEFAULT_SERVER_DATETIME_FORMAT): - raise osv.except_osv(_('Error!'), _('It\'s impossible to confirm an inventory in the future. Please change the inventory date to proceed further.')) #clean the existing inventory lines before redoing an inventory proposal line_ids = [line.id for line in inventory.line_ids] inventory_line_obj.unlink(cr, uid, line_ids, context=context) - th_line_ids = [line.id for line in inventory.theoretical_line_ids] - th_inventory_line_obj.unlink(cr, uid, th_line_ids, context=context) #compute the inventory lines and create them vals = self._get_inventory_lines(cr, uid, inventory, context=context) for product_line in vals: inventory_line_obj.create(cr, uid, product_line, context=context) - th_inventory_line_obj.create(cr, uid, product_line, context=context) - return self.write(cr, uid, ids, {'state': 'confirm'}) + return self.write(cr, uid, ids, {'state': 'confirm', 'date': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)}) def _get_inventory_lines(self, cr, uid, inventory, context=None): location_obj = self.pool.get('stock.location') product_obj = self.pool.get('product.product') location_ids = location_obj.search(cr, uid, [('id', 'child_of', [inventory.location_id.id])], context=context) domain = ' location_id in %s' - args = (inventory.date, inventory.date, tuple(location_ids),) + args = (tuple(location_ids),) if inventory.partner_id: domain += ' and owner_id = %s' args += (inventory.partner_id.id,) @@ -2562,36 +2548,7 @@ class stock_inventory(osv.osv): cr.execute(''' SELECT product_id, sum(qty) as product_qty, location_id, lot_id as prod_lot_id, package_id, owner_id as partner_id - FROM ((SELECT - stock_move.id::text || '-' || quant.id::text AS id, - stock_move.location_dest_id AS location_id, - stock_move.product_id, - quant.qty AS qty, - quant.owner_id, - quant.package_id, - quant.lot_id, - stock_move.date - FROM - stock_quant as quant, stock_quant_move_rel, stock_move - LEFT JOIN stock_location location ON (stock_move.location_dest_id = location.id) - WHERE stock_move.state = 'done' AND location.usage = 'internal' AND stock_quant_move_rel.quant_id = quant.id - AND stock_quant_move_rel.move_id = stock_move.id AND stock_move.date < %s - ) UNION - (SELECT - '-' || stock_move.id::text || '-' || quant.id::text AS id, - stock_move.location_id AS location_id, - stock_move.product_id, - - quant.qty AS qty, - quant.owner_id, - quant.package_id, - quant.lot_id, - stock_move.date - FROM - stock_quant as quant, stock_quant_move_rel, stock_move - LEFT JOIN stock_location location ON (stock_move.location_id = location.id) - WHERE stock_move.state = 'done' AND location.usage = 'internal' AND stock_quant_move_rel.quant_id = quant.id - AND stock_quant_move_rel.move_id = stock_move.id AND stock_move.date < %s - )) AS subquery WHERE''' + domain + ''' + FROM stock_quant WHERE''' + domain + ''' GROUP BY product_id, location_id, lot_id, package_id, partner_id ''', args) vals = [] @@ -2608,20 +2565,6 @@ class stock_inventory(osv.osv): vals.append(product_line) return vals - def _get_th_inventory_lines(self, cr, uid, inventory, context=None): - th_vals = [] - for th_line in inventory.theoretical_line_ids: - th_vals.append({ - 'product_id': th_line.product_id.id, - 'product_uom_id': th_line.product_uom_id.id, - 'product_qty': th_line.product_qty, - 'location_id': th_line.location_id.id, - 'prod_lot_id': th_line.prod_lot_id.id, - 'package_id': th_line.package_id.id, - 'partner_id': th_line.partner_id.id - }) - return th_vals - class stock_inventory_line(osv.osv): _name = "stock.inventory.line" @@ -2667,30 +2610,35 @@ class stock_inventory_line(osv.osv): 'product_qty': 1, } - def _resolve_inventory_line(self, cr, uid, inventory_line, theorical_lines, context=None): - #TODO : package_id management ! - found = False - uom_obj = self.pool.get('product.uom') - for th_line in theorical_lines: - #We try to match the inventory line with a theorical line with same product, lot, location and owner - if th_line['location_id'] == inventory_line.location_id.id and th_line['product_id'] == inventory_line.product_id.id and th_line['prod_lot_id'] == inventory_line.prod_lot_id.id and th_line['partner_id'] == inventory_line.partner_id.id: - uom_reference = inventory_line.product_id.uom_id - real_qty = uom_obj._compute_qty_obj(cr, uid, inventory_line.product_uom_id, inventory_line.product_qty, uom_reference) - th_line['product_qty'] -= real_qty - found = True - break - #if it was still not found, we add it to the theorical lines so that it will create a stock move for it - if not found: - vals = { - 'inventory_id': inventory_line.inventory_id.id, - 'location_id': inventory_line.location_id.id, - 'product_id': inventory_line.product_id.id, - 'product_uom_id': inventory_line.product_id.uom_id.id, - 'product_qty': -inventory_line.product_qty, - 'prod_lot_id': inventory_line.prod_lot_id.id, - 'partner_id': inventory_line.partner_id.id, - } - theorical_lines.append(vals) + def _resolve_inventory_line(self, cr, uid, inventory_line, context=None): + stock_move_obj = self.pool.get('stock.move') + diff = inventory_line.th_qty - inventory_line.product_qty + if not diff: + return + #each theorical_lines where difference between theoretical and checked quantities is not 0 is a line for which we need to create a stock move + vals = { + 'name': _('INV:') + (inventory_line.inventory_id.name or ''), + 'product_id': inventory_line.product_id.id, + 'product_uom': inventory_line.product_uom_id.id, + 'date': inventory_line.inventory_id.date, + 'company_id': inventory_line.inventory_id.company_id.id, + 'inventory_id': inventory_line.inventory_id.id, + 'state': 'confirmed', + 'restrict_lot_id': inventory_line.prod_lot_id.id, + 'restrict_partner_id': inventory_line.partner_id.id, + } + inventory_location_id = inventory_line.product_id.property_stock_inventory.id + if diff < 0: + #found more than expected + vals['location_id'] = inventory_location_id + vals['location_dest_id'] = inventory_line.location_id.id + vals['product_uom_qty'] = -diff + else: + #found less than expected + vals['location_id'] = inventory_line.location_id.id + vals['location_dest_id'] = inventory_location_id + vals['product_uom_qty'] = diff + return stock_move_obj.create(cr, uid, vals, context=context) def on_change_product_id(self, cr, uid, ids, location_id, product, uom=False, owner_id=False, lot_id=False, package_id=False, context=None): """ Changes UoM and name if product_id changes. @@ -2716,24 +2664,6 @@ class stock_inventory_line(osv.osv): return {'value': {'th_qty': th_qty, 'product_uom_id': uom or obj_product.uom_id.id}} -class stock_theoretical_inventory_line(osv.osv): - """ Simple copy of the inventory line table used to keep track of the theoretical inventory lines that should be recorded in inventories. It's filled at the time an inventory is started and checked and the time it's done to see the difference and apply stock move accordingly. We could have used another relation between inventory and inventory lines but having a second table is easier to make sure we don't introduce any side effect """ - _name = "stock.theoretical.inventory.line" - _columns = { - 'inventory_id': fields.many2one('stock.inventory', 'Inventory', ondelete='cascade', select=True), - 'location_id': fields.many2one('stock.location', 'Location', required=True, select=True), - 'product_id': fields.many2one('product.product', 'Product', required=True, select=True), - 'package_id': fields.many2one('stock.quant.package', 'Pack', select=True), - 'product_uom_id': fields.many2one('product.uom', 'Product Unit of Measure', required=True), - 'product_qty': fields.float('Checked Quantity', digits_compute=dp.get_precision('Product Unit of Measure')), - 'company_id': fields.related('inventory_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, select=True, readonly=True), - 'prod_lot_id': fields.many2one('stock.production.lot', 'Serial Number', domain="[('product_id','=',product_id)]"), - 'state': fields.related('inventory_id', 'state', type='char', string='Status', readonly=True), - 'th_qty': fields.float('Theoretical Quantity', readonly=True), - 'partner_id': fields.many2one('res.partner', 'Owner'), - } - - #---------------------------------------------------------- # Stock Warehouse #---------------------------------------------------------- diff --git a/addons/stock/stock_view.xml b/addons/stock/stock_view.xml index 27af1842645..64539be3cc8 100644 --- a/addons/stock/stock_view.xml +++ b/addons/stock/stock_view.xml @@ -138,6 +138,9 @@ +

+ Inventory adjustments will be made by comparing the theoretical and the checked quantities. You can delete lines to ignore some products. If a product is not at the right place, set the checked quantity to 0 and create a new line with correct location. +

From c8e1b778b0e064ab3d05f9038a4ec059271ee743 Mon Sep 17 00:00:00 2001 From: "Quentin (OpenERP)" Date: Thu, 10 Apr 2014 12:14:04 +0200 Subject: [PATCH 6/6] [IMP] stock, inventory: improving the usability bzr revid: qdp-launchpad@openerp.com-20140410101404-uvufnxi4p6s4aicu --- addons/stock/stock.py | 43 +++++++++++++++++++++++-------------- addons/stock/stock_view.xml | 21 ++++++++++-------- 2 files changed, 39 insertions(+), 25 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 96ec2c1d4fe..3c7f8499293 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -2640,28 +2640,39 @@ class stock_inventory_line(osv.osv): vals['product_uom_qty'] = diff return stock_move_obj.create(cr, uid, vals, context=context) - def on_change_product_id(self, cr, uid, ids, location_id, product, uom=False, owner_id=False, lot_id=False, package_id=False, context=None): - """ Changes UoM and name if product_id changes. + def restrict_change(self, cr, uid, ids, th_qty, context=None): + if ids and th_qty: + #if the user try to modify a line prepared by openerp, reject the change and display an error message explaining how he should do + old_value = self.browse(cr, uid, ids[0], context=context) + return { + 'value': { + 'product_id': old_value.product_id.id, + 'product_uom_id': old_value.product_uom_id.id, + 'location_id': old_value.location_id.id, + 'prod_lot_id': old_value.prod_lot_id.id, + 'package_id': old_value.package_id.id, + 'partner_id': old_value.partner_id.id, + }, + 'warning': { + 'title': _('Error'), + 'message': _('You can only change the checked quantity of an existing inventory line. If you want modify a data, please set the checked quantity to 0 and create a new inventory line.') + } + } + return {} + + def on_change_product_id(self, cr, uid, ids, product, uom, th_qty, context=None): + """ Changes UoM @param location_id: Location id @param product: Changed product_id @param uom: UoM product @return: Dictionary of changed values """ - context = context or {} + if ids and th_qty: + return self.restrict_change(cr, uid, ids, th_qty, context=context) if not product: - return {'value': {'product_qty': 0.0, 'product_uom_id': False}} - uom_obj = self.pool.get('product.uom') - ctx = context.copy() - ctx['location'] = location_id - ctx['lot_id'] = lot_id - ctx['owner_id'] = owner_id - ctx['package_id'] = package_id - obj_product = self.pool.get('product.product').browse(cr, uid, product, context=ctx) - th_qty = obj_product.qty_available - if uom and uom != obj_product.uom_id.id: - uom_record = uom_obj.browse(cr, uid, uom, context=context) - th_qty = uom_obj._compute_qty_obj(cr, uid, obj_product.uom_id, th_qty, uom_record) - return {'value': {'th_qty': th_qty, 'product_uom_id': uom or obj_product.uom_id.id}} + return {'value': {'product_uom_id': False}} + obj_product = self.pool.get('product.product').browse(cr, uid, product, context=context) + return {'value': {'product_uom_id': uom or obj_product.uom_id.id}} #---------------------------------------------------------- diff --git a/addons/stock/stock_view.xml b/addons/stock/stock_view.xml index 64539be3cc8..3889f798ab6 100644 --- a/addons/stock/stock_view.xml +++ b/addons/stock/stock_view.xml @@ -127,20 +127,23 @@