From 806d0a52a150d04fbf7d2f46aa69977a36d4fe3d Mon Sep 17 00:00:00 2001 From: Simon Lejeune Date: Sat, 11 May 2019 10:15:26 +0000 Subject: [PATCH 01/34] [REF] stock_account: stock inventory to is own file No changes on the code. task-1875873 --- addons/stock_account/models/__init__.py | 1 + addons/stock_account/models/stock.py | 39 ----------------- .../stock_account/models/stock_inventory.py | 43 +++++++++++++++++++ 3 files changed, 44 insertions(+), 39 deletions(-) create mode 100644 addons/stock_account/models/stock_inventory.py diff --git a/addons/stock_account/models/__init__.py b/addons/stock_account/models/__init__.py index a11616c850a..ee0668460d2 100644 --- a/addons/stock_account/models/__init__.py +++ b/addons/stock_account/models/__init__.py @@ -7,4 +7,5 @@ from . import account_move from . import account_reconciliation_widget from . import product from . import stock +from . import stock_inventory from . import res_config_settings diff --git a/addons/stock_account/models/stock.py b/addons/stock_account/models/stock.py index 1deebbbb0db..c0ca61000fd 100644 --- a/addons/stock_account/models/stock.py +++ b/addons/stock_account/models/stock.py @@ -11,45 +11,6 @@ import logging _logger = logging.getLogger(__name__) -class StockInventory(models.Model): - _inherit = "stock.inventory" - - accounting_date = fields.Date( - 'Accounting Date', - help="Date at which the accounting entries will be created" - " in case of automated inventory valuation." - " If empty, the inventory date will be used.") - has_account_moves = fields.Boolean(compute='_compute_has_account_moves') - - def _compute_has_account_moves(self): - for inventory in self: - if inventory.state == 'done' and inventory.move_ids: - account_move = self.env['account.move'].search_count([ - ('stock_move_id.id', 'in', inventory.move_ids.ids) - ]) - inventory.has_account_moves = account_move > 0 - else: - inventory.has_account_moves = False - - def action_get_account_moves(self): - self.ensure_one() - action_ref = self.env.ref('account.action_move_journal_line') - if not action_ref: - return False - action_data = action_ref.read()[0] - action_data['domain'] = [('stock_move_id.id', 'in', self.move_ids.ids)] - return action_data - - @api.multi - def post_inventory(self): - acc_inventories = self.filtered(lambda inventory: inventory.accounting_date) - for inventory in acc_inventories: - super(StockInventory, inventory.with_context(force_period_date=inventory.accounting_date)).post_inventory() - other_inventories = self - acc_inventories - if other_inventories: - super(StockInventory, other_inventories).post_inventory() - - class StockLocation(models.Model): _inherit = "stock.location" diff --git a/addons/stock_account/models/stock_inventory.py b/addons/stock_account/models/stock_inventory.py new file mode 100644 index 00000000000..a5ae3bfe0a2 --- /dev/null +++ b/addons/stock_account/models/stock_inventory.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import fields, models + + +class StockInventory(models.Model): + _inherit = "stock.inventory" + + accounting_date = fields.Date( + 'Accounting Date', + help="Date at which the accounting entries will be created" + " in case of automated inventory valuation." + " If empty, the inventory date will be used.") + has_account_moves = fields.Boolean(compute='_compute_has_account_moves') + + def _compute_has_account_moves(self): + for inventory in self: + if inventory.state == 'done' and inventory.move_ids: + account_move = self.env['account.move'].search_count([ + ('stock_move_id.id', 'in', inventory.move_ids.ids) + ]) + inventory.has_account_moves = account_move > 0 + else: + inventory.has_account_moves = False + + def action_get_account_moves(self): + self.ensure_one() + action_ref = self.env.ref('account.action_move_journal_line') + if not action_ref: + return False + action_data = action_ref.read()[0] + action_data['domain'] = [('stock_move_id.id', 'in', self.move_ids.ids)] + return action_data + + def post_inventory(self): + acc_inventories = self.filtered(lambda inventory: inventory.accounting_date) + for inventory in acc_inventories: + super(StockInventory, inventory.with_context(force_period_date=inventory.accounting_date)).post_inventory() + other_inventories = self - acc_inventories + if other_inventories: + super(StockInventory, other_inventories).post_inventory() + From 95b4fd1a259bb1a76db7c6c7325dcfe88ddbc286 Mon Sep 17 00:00:00 2001 From: Simon Lejeune Date: Sat, 11 May 2019 10:21:24 +0000 Subject: [PATCH 02/34] [REF] stock_account: stock location to its own file No changes on the code. task-1875873 --- addons/stock_account/models/__init__.py | 1 + addons/stock_account/models/stock.py | 28 ---------------- addons/stock_account/models/stock_location.py | 33 +++++++++++++++++++ 3 files changed, 34 insertions(+), 28 deletions(-) create mode 100644 addons/stock_account/models/stock_location.py diff --git a/addons/stock_account/models/__init__.py b/addons/stock_account/models/__init__.py index ee0668460d2..b5f22d496ae 100644 --- a/addons/stock_account/models/__init__.py +++ b/addons/stock_account/models/__init__.py @@ -8,4 +8,5 @@ from . import account_reconciliation_widget from . import product from . import stock from . import stock_inventory +from . import stock_location from . import res_config_settings diff --git a/addons/stock_account/models/stock.py b/addons/stock_account/models/stock.py index c0ca61000fd..1dafcf630ab 100644 --- a/addons/stock_account/models/stock.py +++ b/addons/stock_account/models/stock.py @@ -11,34 +11,6 @@ import logging _logger = logging.getLogger(__name__) -class StockLocation(models.Model): - _inherit = "stock.location" - - valuation_in_account_id = fields.Many2one( - 'account.account', 'Stock Valuation Account (Incoming)', - domain=[('internal_type', '=', 'other'), ('deprecated', '=', False)], - help="Used for real-time inventory valuation. When set on a virtual location (non internal type), " - "this account will be used to hold the value of products being moved from an internal location " - "into this location, instead of the generic Stock Output Account set on the product. " - "This has no effect for internal locations.") - valuation_out_account_id = fields.Many2one( - 'account.account', 'Stock Valuation Account (Outgoing)', - domain=[('internal_type', '=', 'other'), ('deprecated', '=', False)], - help="Used for real-time inventory valuation. When set on a virtual location (non internal type), " - "this account will be used to hold the value of products being moved out of this location " - "and into an internal location, instead of the generic Stock Output Account set on the product. " - "This has no effect for internal locations.") - - def _should_be_valued(self): - """ This method returns a boolean reflecting whether the products stored in `self` should - be considered when valuating the stock of a company. - """ - self.ensure_one() - if self.usage == 'internal' or (self.usage == 'transit' and self.company_id): - return True - return False - - class StockMoveLine(models.Model): _inherit = 'stock.move.line' diff --git a/addons/stock_account/models/stock_location.py b/addons/stock_account/models/stock_location.py new file mode 100644 index 00000000000..731e31b3f4d --- /dev/null +++ b/addons/stock_account/models/stock_location.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import fields, models + + +class StockLocation(models.Model): + _inherit = "stock.location" + + valuation_in_account_id = fields.Many2one( + 'account.account', 'Stock Valuation Account (Incoming)', + domain=[('internal_type', '=', 'other'), ('deprecated', '=', False)], + help="Used for real-time inventory valuation. When set on a virtual location (non internal type), " + "this account will be used to hold the value of products being moved from an internal location " + "into this location, instead of the generic Stock Output Account set on the product. " + "This has no effect for internal locations.") + valuation_out_account_id = fields.Many2one( + 'account.account', 'Stock Valuation Account (Outgoing)', + domain=[('internal_type', '=', 'other'), ('deprecated', '=', False)], + help="Used for real-time inventory valuation. When set on a virtual location (non internal type), " + "this account will be used to hold the value of products being moved out of this location " + "and into an internal location, instead of the generic Stock Output Account set on the product. " + "This has no effect for internal locations.") + + def _should_be_valued(self): + """ This method returns a boolean reflecting whether the products stored in `self` should + be considered when valuating the stock of a company. + """ + self.ensure_one() + if self.usage == 'internal' or (self.usage == 'transit' and self.company_id): + return True + return False + From da983190c8fc7e1d5ca7dc07b4647d1a2bedd7a8 Mon Sep 17 00:00:00 2001 From: Simon Lejeune Date: Sat, 11 May 2019 10:23:20 +0000 Subject: [PATCH 03/34] [REF] stock_account: stock move line to its own file No changes on the code. task-1875873 --- addons/stock_account/models/__init__.py | 1 + addons/stock_account/models/stock.py | 71 ------------------ .../stock_account/models/stock_move_line.py | 75 +++++++++++++++++++ 3 files changed, 76 insertions(+), 71 deletions(-) create mode 100644 addons/stock_account/models/stock_move_line.py diff --git a/addons/stock_account/models/__init__.py b/addons/stock_account/models/__init__.py index b5f22d496ae..585adb0ae49 100644 --- a/addons/stock_account/models/__init__.py +++ b/addons/stock_account/models/__init__.py @@ -9,4 +9,5 @@ from . import product from . import stock from . import stock_inventory from . import stock_location +from . import stock_move_line from . import res_config_settings diff --git a/addons/stock_account/models/stock.py b/addons/stock_account/models/stock.py index 1dafcf630ab..03788b92215 100644 --- a/addons/stock_account/models/stock.py +++ b/addons/stock_account/models/stock.py @@ -11,77 +11,6 @@ import logging _logger = logging.getLogger(__name__) -class StockMoveLine(models.Model): - _inherit = 'stock.move.line' - - @api.model_create_multi - def create(self, vals_list): - lines = super(StockMoveLine, self).create(vals_list) - for line in lines: - move = line.move_id - if move.state == 'done': - correction_value = move._run_valuation(line.qty_done) - if move.product_id.valuation == 'real_time' and (move._is_in() or move._is_out()): - move.with_context(force_valuation_amount=correction_value)._account_entry_move() - return lines - - @api.multi - def write(self, vals): - """ When editing a done stock.move.line, we impact the valuation. Users may increase or - decrease the `qty_done` field. There are three cost method available: standard, average - and fifo. We implement the logic in a similar way for standard and average: increase - or decrease the original value with the standard or average price of today. In fifo, we - have a different logic wheter the move is incoming or outgoing. If the move is incoming, we - update the value and remaining_value/qty with the unit price of the move. If the move is - outgoing and the user increases qty_done, we call _run_fifo and it'll consume layer(s) in - the stack the same way a new outgoing move would have done. If the move is outoing and the - user decreases qty_done, we either increase the last receipt candidate if one is found or - we decrease the value with the last fifo price. - """ - if 'qty_done' in vals: - moves_to_update = {} - for move_line in self.filtered(lambda ml: ml.state == 'done' and (ml.move_id._is_in() or ml.move_id._is_out())): - moves_to_update[move_line.move_id] = vals['qty_done'] - move_line.qty_done - - for move_id, qty_difference in moves_to_update.items(): - move_vals = {} - if move_id.product_id.cost_method in ['standard', 'average']: - correction_value = qty_difference * move_id.product_id.standard_price - if move_id._is_in(): - move_vals['value'] = move_id.value + correction_value - elif move_id._is_out(): - move_vals['value'] = move_id.value - correction_value - else: - if move_id._is_in(): - correction_value = qty_difference * move_id.price_unit - new_remaining_value = move_id.remaining_value + correction_value - move_vals['value'] = move_id.value + correction_value - move_vals['remaining_qty'] = move_id.remaining_qty + qty_difference - move_vals['remaining_value'] = move_id.remaining_value + correction_value - elif move_id._is_out() and qty_difference > 0: - correction_value = self.env['stock.move']._run_fifo(move_id, quantity=qty_difference) - # no need to adapt `remaining_qty` and `remaining_value` as `_run_fifo` took care of it - move_vals['value'] = move_id.value - correction_value - elif move_id._is_out() and qty_difference < 0: - candidates_receipt = self.env['stock.move'].search(move_id._get_in_domain(), order='date, id desc', limit=1) - if candidates_receipt: - candidates_receipt.write({ - 'remaining_qty': candidates_receipt.remaining_qty + -qty_difference, - 'remaining_value': candidates_receipt.remaining_value + (-qty_difference * candidates_receipt.price_unit), - }) - correction_value = qty_difference * candidates_receipt.price_unit - else: - correction_value = qty_difference * move_id.product_id.standard_price - move_vals['value'] = move_id.value - correction_value - move_id.write(move_vals) - - if move_id.product_id.valuation == 'real_time': - move_id.with_context(force_valuation_amount=correction_value, forced_quantity=qty_difference)._account_entry_move() - if qty_difference > 0: - move_id.product_price_update_before_done(forced_qty=qty_difference) - return super(StockMoveLine, self).write(vals) - - class StockMove(models.Model): _inherit = "stock.move" diff --git a/addons/stock_account/models/stock_move_line.py b/addons/stock_account/models/stock_move_line.py new file mode 100644 index 00000000000..838a6bb4b04 --- /dev/null +++ b/addons/stock_account/models/stock_move_line.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, models + + +class StockMoveLine(models.Model): + _inherit = 'stock.move.line' + + @api.model_create_multi + def create(self, vals_list): + lines = super(StockMoveLine, self).create(vals_list) + for line in lines: + move = line.move_id + if move.state == 'done': + correction_value = move._run_valuation(line.qty_done) + if move.product_id.valuation == 'real_time' and (move._is_in() or move._is_out()): + move.with_context(force_valuation_amount=correction_value)._account_entry_move() + return lines + + def write(self, vals): + """ When editing a done stock.move.line, we impact the valuation. Users may increase or + decrease the `qty_done` field. There are three cost method available: standard, average + and fifo. We implement the logic in a similar way for standard and average: increase + or decrease the original value with the standard or average price of today. In fifo, we + have a different logic wheter the move is incoming or outgoing. If the move is incoming, we + update the value and remaining_value/qty with the unit price of the move. If the move is + outgoing and the user increases qty_done, we call _run_fifo and it'll consume layer(s) in + the stack the same way a new outgoing move would have done. If the move is outoing and the + user decreases qty_done, we either increase the last receipt candidate if one is found or + we decrease the value with the last fifo price. + """ + if 'qty_done' in vals: + moves_to_update = {} + for move_line in self.filtered(lambda ml: ml.state == 'done' and (ml.move_id._is_in() or ml.move_id._is_out())): + moves_to_update[move_line.move_id] = vals['qty_done'] - move_line.qty_done + + for move_id, qty_difference in moves_to_update.items(): + move_vals = {} + if move_id.product_id.cost_method in ['standard', 'average']: + correction_value = qty_difference * move_id.product_id.standard_price + if move_id._is_in(): + move_vals['value'] = move_id.value + correction_value + elif move_id._is_out(): + move_vals['value'] = move_id.value - correction_value + else: + if move_id._is_in(): + correction_value = qty_difference * move_id.price_unit + new_remaining_value = move_id.remaining_value + correction_value + move_vals['value'] = move_id.value + correction_value + move_vals['remaining_qty'] = move_id.remaining_qty + qty_difference + move_vals['remaining_value'] = move_id.remaining_value + correction_value + elif move_id._is_out() and qty_difference > 0: + correction_value = self.env['stock.move']._run_fifo(move_id, quantity=qty_difference) + # no need to adapt `remaining_qty` and `remaining_value` as `_run_fifo` took care of it + move_vals['value'] = move_id.value - correction_value + elif move_id._is_out() and qty_difference < 0: + candidates_receipt = self.env['stock.move'].search(move_id._get_in_domain(), order='date, id desc', limit=1) + if candidates_receipt: + candidates_receipt.write({ + 'remaining_qty': candidates_receipt.remaining_qty + -qty_difference, + 'remaining_value': candidates_receipt.remaining_value + (-qty_difference * candidates_receipt.price_unit), + }) + correction_value = qty_difference * candidates_receipt.price_unit + else: + correction_value = qty_difference * move_id.product_id.standard_price + move_vals['value'] = move_id.value - correction_value + move_id.write(move_vals) + + if move_id.product_id.valuation == 'real_time': + move_id.with_context(force_valuation_amount=correction_value, forced_quantity=qty_difference)._account_entry_move() + if qty_difference > 0: + move_id.product_price_update_before_done(forced_qty=qty_difference) + return super(StockMoveLine, self).write(vals) + From d6b35361197d94de192f73853c342e749c6008e0 Mon Sep 17 00:00:00 2001 From: Simon Lejeune Date: Sat, 11 May 2019 10:28:31 +0000 Subject: [PATCH 04/34] [REF] stock_account: procurement group to its own file No changes on the code. task-1875873 --- addons/stock_account/models/__init__.py | 1 + addons/stock_account/models/procurement_group.py | 14 ++++++++++++++ addons/stock_account/models/stock.py | 7 ------- 3 files changed, 15 insertions(+), 7 deletions(-) create mode 100644 addons/stock_account/models/procurement_group.py diff --git a/addons/stock_account/models/__init__.py b/addons/stock_account/models/__init__.py index 585adb0ae49..a83714e5aa3 100644 --- a/addons/stock_account/models/__init__.py +++ b/addons/stock_account/models/__init__.py @@ -5,6 +5,7 @@ from . import account_chart_template from . import account_invoice from . import account_move from . import account_reconciliation_widget +from . import procurement_group from . import product from . import stock from . import stock_inventory diff --git a/addons/stock_account/models/procurement_group.py b/addons/stock_account/models/procurement_group.py new file mode 100644 index 00000000000..2372c13bd57 --- /dev/null +++ b/addons/stock_account/models/procurement_group.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, models + + +class ProcurementGroup(models.Model): + _inherit = 'procurement.group' + + @api.model + def _run_scheduler_tasks(self, use_new_cursor=False, company_id=False): + super(ProcurementGroup, self)._run_scheduler_tasks(use_new_cursor=use_new_cursor, company_id=company_id) + self.env['stock.move']._run_fifo_vacuum() + diff --git a/addons/stock_account/models/stock.py b/addons/stock_account/models/stock.py index 03788b92215..cf4d9176639 100644 --- a/addons/stock_account/models/stock.py +++ b/addons/stock_account/models/stock.py @@ -622,10 +622,3 @@ class StockReturnPickingLine(models.TransientModel): to_refund = fields.Boolean(string="Update quantities on SO/PO", help='Trigger a decrease of the delivered/received quantity in the associated Sale Order/Purchase Order') -class ProcurementGroup(models.Model): - _inherit = 'procurement.group' - - @api.model - def _run_scheduler_tasks(self, use_new_cursor=False, company_id=False): - super(ProcurementGroup, self)._run_scheduler_tasks(use_new_cursor=use_new_cursor, company_id=company_id) - self.env['stock.move']._run_fifo_vacuum() From e9ca33de8d4c5614ea05fb7975fdfcb4d527f17e Mon Sep 17 00:00:00 2001 From: Simon Lejeune Date: Sat, 11 May 2019 10:37:15 +0000 Subject: [PATCH 05/34] [REF] stock_account: stock picking return to its own file No changes on the code. task-1875873 --- addons/stock_account/models/stock.py | 26 ---------------- addons/stock_account/wizard/__init__.py | 2 +- .../wizard/stock_picking_return.py | 31 +++++++++++++++++++ 3 files changed, 32 insertions(+), 27 deletions(-) create mode 100644 addons/stock_account/wizard/stock_picking_return.py diff --git a/addons/stock_account/models/stock.py b/addons/stock_account/models/stock.py index cf4d9176639..cd3c2b62870 100644 --- a/addons/stock_account/models/stock.py +++ b/addons/stock_account/models/stock.py @@ -596,29 +596,3 @@ class StockMove(models.Model): """ return self.env['account.invoice'] - -class StockReturnPicking(models.TransientModel): - _inherit = "stock.return.picking" - - @api.multi - def _create_returns(self): - new_picking_id, pick_type_id = super(StockReturnPicking, self)._create_returns() - new_picking = self.env['stock.picking'].browse([new_picking_id]) - for move in new_picking.move_lines: - return_picking_line = self.product_return_moves.filtered(lambda r: r.move_id == move.origin_returned_move_id) - if return_picking_line and return_picking_line.to_refund: - move.to_refund = True - return new_picking_id, pick_type_id - - def _prepare_stock_return_picking_line_vals_from_move(self, stock_move): - res = super(StockReturnPicking, self)._prepare_stock_return_picking_line_vals_from_move(stock_move) - res['to_refund'] = True - return res - - -class StockReturnPickingLine(models.TransientModel): - _inherit = "stock.return.picking.line" - - to_refund = fields.Boolean(string="Update quantities on SO/PO", help='Trigger a decrease of the delivered/received quantity in the associated Sale Order/Purchase Order') - - diff --git a/addons/stock_account/wizard/__init__.py b/addons/stock_account/wizard/__init__.py index 01b730d1fe4..0d966f31f09 100644 --- a/addons/stock_account/wizard/__init__.py +++ b/addons/stock_account/wizard/__init__.py @@ -3,4 +3,4 @@ from . import stock_change_standard_price from . import stock_quantity_history - +from . import stock_picking_return diff --git a/addons/stock_account/wizard/stock_picking_return.py b/addons/stock_account/wizard/stock_picking_return.py new file mode 100644 index 00000000000..35a9f92ab94 --- /dev/null +++ b/addons/stock_account/wizard/stock_picking_return.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, fields, models + + +class StockReturnPicking(models.TransientModel): + _inherit = "stock.return.picking" + + @api.model + def default_get(self, default_fields): + res = super(StockReturnPicking, self).default_get(default_fields) + for i, k, vals in res.get('product_return_moves', []): + vals.update({'to_refund': True}) + return res + + def _create_returns(self): + new_picking_id, pick_type_id = super(StockReturnPicking, self)._create_returns() + new_picking = self.env['stock.picking'].browse([new_picking_id]) + for move in new_picking.move_lines: + return_picking_line = self.product_return_moves.filtered(lambda r: r.move_id == move.origin_returned_move_id) + if return_picking_line and return_picking_line.to_refund: + move.to_refund = True + return new_picking_id, pick_type_id + + +class StockReturnPickingLine(models.TransientModel): + _inherit = "stock.return.picking.line" + + to_refund = fields.Boolean(string="Update quantities on SO/PO", help='Trigger a decrease of the delivered/received quantity in the associated Sale Order/Purchase Order') + From f81eb396e96c15051bd00a3a17b01470d1209172 Mon Sep 17 00:00:00 2001 From: Simon Lejeune Date: Sat, 11 May 2019 10:51:45 +0000 Subject: [PATCH 06/34] [REF] stock_account: stock move to its own file No changes on the code. Actually a rename of `stock.py`. task-1875873 --- addons/stock_account/models/__init__.py | 2 +- addons/stock_account/models/{stock.py => stock_move.py} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename addons/stock_account/models/{stock.py => stock_move.py} (100%) diff --git a/addons/stock_account/models/__init__.py b/addons/stock_account/models/__init__.py index a83714e5aa3..134f11a2a47 100644 --- a/addons/stock_account/models/__init__.py +++ b/addons/stock_account/models/__init__.py @@ -7,7 +7,7 @@ from . import account_move from . import account_reconciliation_widget from . import procurement_group from . import product -from . import stock +from . import stock_move from . import stock_inventory from . import stock_location from . import stock_move_line diff --git a/addons/stock_account/models/stock.py b/addons/stock_account/models/stock_move.py similarity index 100% rename from addons/stock_account/models/stock.py rename to addons/stock_account/models/stock_move.py From f30ca1687d31638b679c3b4df0616f31ac7d5776 Mon Sep 17 00:00:00 2001 From: Simon Lejeune Date: Mon, 6 May 2019 11:30:35 +0000 Subject: [PATCH 07/34] [FIX] purchase_stock: don't call _is_dropshipped on an empty recordset We will enforce an `ensure_one` in a further commit. task-1875873 --- addons/purchase_stock/models/purchase.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/purchase_stock/models/purchase.py b/addons/purchase_stock/models/purchase.py index c652940b2e9..077ab49487d 100644 --- a/addons/purchase_stock/models/purchase.py +++ b/addons/purchase_stock/models/purchase.py @@ -249,7 +249,7 @@ class PurchaseOrderLine(models.Model): if move.location_dest_id.usage == "supplier": if move.to_refund: total -= move.product_uom._compute_quantity(move.product_uom_qty, line.product_uom) - elif move.origin_returned_move_id._is_dropshipped() and not move._is_dropshipped_returned(): + elif move.origin_returned_move_id and move.origin_returned_move_id._is_dropshipped() and not move._is_dropshipped_returned(): # Edge case: the dropship is returned to the stock, no to the supplier. # In this case, the received quantity on the PO is set although we didn't # receive the product physically in our stock. To avoid counting the From 72693c1d0a6e1053fceed4154c8d46c11917d4a3 Mon Sep 17 00:00:00 2001 From: Simon Lejeune Date: Thu, 9 May 2019 09:06:57 +0000 Subject: [PATCH 08/34] [REF] stock_account: make property_stock_journal required in the view As journal_id is required on an account move, this field should not be treated differently as the other accounting fields on this view. task-1875873 --- addons/stock_account/views/product_views.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/stock_account/views/product_views.xml b/addons/stock_account/views/product_views.xml index b4fd4e46503..1b9013e9e04 100644 --- a/addons/stock_account/views/product_views.xml +++ b/addons/stock_account/views/product_views.xml @@ -12,7 +12,7 @@ - + From 0477bdb75afafa62b204f29e9dbfc3fc5fd012d4 Mon Sep 17 00:00:00 2001 From: Simon Lejeune Date: Fri, 10 May 2019 14:11:32 +0000 Subject: [PATCH 09/34] [REF] product, stock_account, etc: remove product.price.history table This table tracked standard price changes. It was used to compute the valuation at date in AVCO and standard through `get_history_price`. The next commits will introduce the stock valuation layers, separating the valuation from the stock move. As a valuation layer will be created when a standard price is updated on the product or when a stock move impacts the valuation, the information from this table will be duplicated. We don't plan to adapt `get_history_price` to work with the valuation layer since the valuation report at date and today will be the same: a grouped list on the valuation layers with eventually a domain on the date. task-1875873 --- addons/account/security/ir.model.access.csv | 1 - .../event_sale/security/ir.model.access.csv | 1 - addons/mrp/security/ir.model.access.csv | 1 - .../security/ir.model.access.csv | 1 - addons/product/models/product.py | 44 ------------------- addons/product/security/ir.model.access.csv | 2 - addons/purchase/security/ir.model.access.csv | 4 +- addons/sale/security/ir.model.access.csv | 1 - addons/stock/security/ir.model.access.csv | 2 - .../tests/test_stockvaluation.py | 2 + 10 files changed, 3 insertions(+), 56 deletions(-) diff --git a/addons/account/security/ir.model.access.csv b/addons/account/security/ir.model.access.csv index 27828b3824c..467a9af3fb5 100644 --- a/addons/account/security/ir.model.access.csv +++ b/addons/account/security/ir.model.access.csv @@ -2,7 +2,6 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_product_product_account_user,product.product.account.user,product.model_product_product,group_account_user,1,0,0,0 access_product_product_account_manager,product.product.account.manager,product.model_product_product,account.group_account_manager,1,1,1,1 access_product_template_account_manager,product.template.account.manager,product.model_product_template,account.group_account_manager,1,1,1,1 -access_product_price_history_account_manager,product.price.history.account.manager,product.model_product_price_history,account.group_account_manager,1,1,1,1 access_account_payment_term,account.payment.term,model_account_payment_term,account.group_account_user,1,0,0,0 access_account_payment_term_line,account.payment.term.line,model_account_payment_term_line,account.group_account_user,1,0,0,0 access_account_account_type_manager,account.account.type,model_account_account_type,account.group_account_manager,1,1,1,1 diff --git a/addons/event_sale/security/ir.model.access.csv b/addons/event_sale/security/ir.model.access.csv index 87433e49dcf..c8ea33e6655 100644 --- a/addons/event_sale/security/ir.model.access.csv +++ b/addons/event_sale/security/ir.model.access.csv @@ -3,4 +3,3 @@ access_event_event_ticket_user,event.event.ticket.user,event_sale.model_event_ev access_event_event_ticket_admin,event.event.ticket.admin,event_sale.model_event_event_ticket,event.group_event_manager,1,1,1,1 access_product_template_event_manager,product.template.event.manager,product.model_product_template,event.group_event_manager,1,1,1,1 access_product_product_event_manager,product.product.event.manager,product.model_product_product,event.group_event_manager,1,1,1,1 -access_product_price_history_event_manager,product.price.history.event.manager,product.model_product_price_history,event.group_event_manager,1,1,1,1 diff --git a/addons/mrp/security/ir.model.access.csv b/addons/mrp/security/ir.model.access.csv index ab595d23776..7bb417338a7 100644 --- a/addons/mrp/security/ir.model.access.csv +++ b/addons/mrp/security/ir.model.access.csv @@ -24,7 +24,6 @@ access_stock_warehouse,stock.warehouse mrp_worker,stock.model_stock_warehouse,mr access_mrp_production_stock_worker,mrp.production stock_worker,model_mrp_production,stock.group_stock_user,1,0,0,0 access_product_product_user,product.product user,product.model_product_product,mrp.group_mrp_user,1,0,0,0 access_product_template_user,product.template user,product.model_product_template,mrp.group_mrp_user,1,0,0,0 -access_product_price_history_mrp_manager,product.price.history.mrp.manager,product.model_product_price_history,mrp.group_mrp_manager,1,1,1,1 access_uom_uom_user,uom.uom user,uom.model_uom_uom,mrp.group_mrp_user,1,0,0,0 access_product_supplierinfo_user,product.supplierinfo user,product.model_product_supplierinfo,mrp.group_mrp_user,1,1,1,1 access_res_partner,res.partner,base.model_res_partner,mrp.group_mrp_user,1,0,0,0 diff --git a/addons/point_of_sale/security/ir.model.access.csv b/addons/point_of_sale/security/ir.model.access.csv index 1a3a506b90e..39a33c6b31f 100644 --- a/addons/point_of_sale/security/ir.model.access.csv +++ b/addons/point_of_sale/security/ir.model.access.csv @@ -16,7 +16,6 @@ access_product_product,product.product,product.model_product_product,group_pos_u access_product_template_pos_user,product.template pos user,product.model_product_template,group_pos_user,1,0,0,0 access_product_product_pos_manager,product.product.pos manager,product.model_product_product,group_pos_manager,1,1,1,1 access_product_template_pos_manager,product.template pos manager,product.model_product_template,group_pos_manager,1,1,1,1 -access_product_price_history_pos_manager,product.price.history.pos.manager,product.model_product_price_history,group_pos_manager,1,1,1,1 access_account_bank_statement_line_manager,account.bank.statement.line manager,account.model_account_bank_statement_line,group_pos_manager,1,1,1,1 access_product_product_supplierinfo_user,product.supplierinfo user,product.model_product_supplierinfo,group_pos_user,1,0,0,0 access_product_product_packaging_user,product.packaging user,product.model_product_packaging,group_pos_user,1,0,0,0 diff --git a/addons/product/models/product.py b/addons/product/models/product.py index a8df285d257..936b3350f54 100644 --- a/addons/product/models/product.py +++ b/addons/product/models/product.py @@ -63,23 +63,6 @@ class ProductCategory(models.Model): return self.create({'name': name}).name_get()[0] -class ProductPriceHistory(models.Model): - """ Keep track of the ``product.template`` standard prices as they are changed. """ - _name = 'product.price.history' - _rec_name = 'datetime' - _order = 'datetime desc' - _description = 'Product Price List History' - - def _get_default_company_id(self): - return self._context.get('force_company', self.env.company.id) - - company_id = fields.Many2one('res.company', string='Company', - default=_get_default_company_id, required=True) - product_id = fields.Many2one('product.product', 'Product', ondelete='cascade', required=True) - datetime = fields.Datetime('Date', default=fields.Datetime.now) - cost = fields.Float('Cost', digits=dp.get_precision('Product Price')) - - class ProductProduct(models.Model): _name = "product.product" _description = "Product" @@ -386,10 +369,6 @@ class ProductProduct(models.Model): @api.model_create_multi def create(self, vals_list): products = super(ProductProduct, self.with_context(create_product_product=True)).create(vals_list) - for product, vals in zip(products, vals_list): - # When a unique variant is created from tmpl then the standard price is set by _set_standard_price - if not (self.env.context.get('create_from_tmpl') and len(product.product_tmpl_id.product_variant_ids) == 1): - product._set_standard_price(vals.get('standard_price') or 0.0) # `_get_variant_id_for_combination` depends on existing variants self.clear_caches() self.env['product.template'].invalidate_cache( @@ -406,10 +385,7 @@ class ProductProduct(models.Model): @api.multi def write(self, values): - ''' Store the standard price change in order to be able to retrieve the cost of a product for a given date''' res = super(ProductProduct, self).write(values) - if 'standard_price' in values: - self._set_standard_price(values['standard_price']) if 'attribute_value_ids' in values: # `_get_variant_id_for_combination` depends on `attribute_value_ids` self.clear_caches() @@ -686,31 +662,11 @@ class ProductProduct(models.Model): return prices - # compatibility to remove after v10 - DEPRECATED @api.multi def price_get(self, ptype='list_price'): return self.price_compute(ptype) - @api.multi - def _set_standard_price(self, value): - ''' Store the standard price change in order to be able to retrieve the cost of a product for a given date''' - PriceHistory = self.env['product.price.history'] - for product in self: - PriceHistory.create({ - 'product_id': product.id, - 'cost': value, - 'company_id': self._context.get('force_company', self.env.company.id), - }) - - @api.multi - def get_history_price(self, company_id, date=None): - history = self.env['product.price.history'].search([ - ('company_id', '=', company_id), - ('product_id', 'in', self.ids), - ('datetime', '<=', date or fields.Datetime.now())], order='datetime desc,id desc', limit=1) - return history.cost or 0.0 - @api.model def get_empty_list_help(self, help): self = self.with_context( diff --git a/addons/product/security/ir.model.access.csv b/addons/product/security/ir.model.access.csv index 5af96e9dfa9..19aa27fa1bc 100644 --- a/addons/product/security/ir.model.access.csv +++ b/addons/product/security/ir.model.access.csv @@ -7,7 +7,6 @@ access_product_pricelist_user,product.pricelist.user,model_product_pricelist,bas access_product_pricelist_item_user,product.pricelist.item.user,model_product_pricelist_item,base.group_user,1,0,0,0 access_product_pricelist_partner_manager,product.pricelist partner manager,model_product_pricelist,base.group_partner_manager,1,0,0,0 access_product_product_employee,product.product employee,model_product_product,base.group_user,1,0,0,0 -access_product_price_history_employee,prices.history employee,model_product_price_history,base.group_user,1,0,0,0 access_product_attribute,product.attribute,model_product_attribute,base.group_user,1,0,0,0 access_product_attribute_value,product.attribute value,model_product_attribute_value,base.group_user,1,0,0,0 access_product_product_attribute,product.template.attribute value,model_product_template_attribute_value,base.group_user,1,0,0,0 @@ -20,7 +19,6 @@ access_product_supplierinfo_manager,product.supplierinfo.manager,model_product_s access_product_pricelist_manager,product.pricelist.manager,model_product_pricelist,base.group_system,1,1,1,1 access_product_pricelist_item_manager,product.pricelist.item.manager,model_product_pricelist_item,base.group_system,1,1,1,1 access_product_product_manager,product.product manager,model_product_product,base.group_system,1,1,1,1 -access_product_price_history_manager,prices.history manager,model_product_price_history,base.group_system,1,1,1,1 access_product_attribute_manager,product.attribute manager,model_product_attribute,base.group_system,1,1,1,1 access_product_attribute_value_manager,product.attribute value manager,model_product_attribute_value,base.group_system,1,1,1,1 access_product_product_attribute_manager,product.template.attribute value manager,model_product_template_attribute_value,base.group_system,1,1,1,1 diff --git a/addons/purchase/security/ir.model.access.csv b/addons/purchase/security/ir.model.access.csv index e7fa2d8ad40..7c8e0e11ad6 100644 --- a/addons/purchase/security/ir.model.access.csv +++ b/addons/purchase/security/ir.model.access.csv @@ -35,8 +35,6 @@ access_product_group_res_partner_purchase_manager,res_partner group_purchase_man access_product_pricelist_item_purchase_manager,product.pricelist.item purchase_manager,product.model_product_pricelist_item,purchase.group_purchase_manager,1,1,1,1 access_account_account_purchase_manager,account.account purchase manager,account.model_account_account,purchase.group_purchase_manager,1,0,0,0 access_account_journal_purchase_manager,account.journal purchase manager,account.model_account_journal,purchase.group_purchase_manager,1,0,0,0 -access_product_price_history_purchase_user,prices.history purchase user,product.model_product_price_history,purchase.group_purchase_user,1,0,0,0 -access_product_price_history_purchase_manager,prices.history purchase manager,product.model_product_price_history,purchase.group_purchase_manager,1,1,1,1 access_purchase_bill_union,access_purchase_bill_union,model_purchase_bill_union,purchase.group_purchase_user,1,0,0,0 access_report_purchase_order,purchase.stock.report,model_purchase_report,purchase.group_purchase_manager,1,0,0,0 -access_report_purchase_order_user,purchase.stock.report user,model_purchase_report,purchase.group_purchase_user,1,0,0,0 \ No newline at end of file +access_report_purchase_order_user,purchase.stock.report user,model_purchase_report,purchase.group_purchase_user,1,0,0,0 diff --git a/addons/sale/security/ir.model.access.csv b/addons/sale/security/ir.model.access.csv index 476c359c06f..bd3d2e61c49 100644 --- a/addons/sale/security/ir.model.access.csv +++ b/addons/sale/security/ir.model.access.csv @@ -36,7 +36,6 @@ access_account_move_line_salesman,account.move.line invoice,account.model_accoun access_sale_order_invoicing_payments,sale.order,model_sale_order,account.group_account_invoice,1,1,0,0 access_sale_order_line_invoicing_payments,sale.order.line,model_sale_order_line,account.group_account_invoice,1,1,0,0 access_product_pricelist_item_sale_manager,product.pricelist.item salemanager,product.model_product_pricelist_item,sales_team.group_sale_manager,1,1,1,1 -access_product_price_history_salemanager,prices.history sale manager,product.model_product_price_history,sales_team.group_sale_manager,1,1,1,1 access_product_template_sale_manager,product.template salemanager,model_product_template,sales_team.group_sale_manager,1,1,1,1 access_product_product_sale_manager,product.product salemanager,model_product_product,sales_team.group_sale_manager,1,1,1,1 access_product_attribute_sale_manager,product.attribute manager,product.model_product_attribute,sales_team.group_sale_manager,1,1,1,1 diff --git a/addons/stock/security/ir.model.access.csv b/addons/stock/security/ir.model.access.csv index 07d0b9de6d5..5c9eb343604 100644 --- a/addons/stock/security/ir.model.access.csv +++ b/addons/stock/security/ir.model.access.csv @@ -60,8 +60,6 @@ access_stock_putaway_all,stock.putaway.rule all users,model_stock_putaway_rule,b access_stock_putaway_manager,stock.putaway.rule all managers,model_stock_putaway_rule,stock.group_stock_manager,1,1,1,1 access_stock_removal_all,product.removal all users,model_product_removal,base.group_user,1,0,0,0 access_stock_move_line_portal,stock.move.line portal,stock.model_stock_move_line,base.group_portal,0,0,0,0 -access_product_price_history_stock_user,prices.history stock user,product.model_product_price_history,stock.group_stock_user,1,0,0,0 -access_product_price_history_stock_manager,prices.history stock manager,product.model_product_price_history,stock.group_stock_manager,1,1,1,1 access_barcode_nomenclature_stock_user,barcode.nomenclature.stock.user,barcodes.model_barcode_nomenclature,stock.group_stock_user,1,0,0,0 access_barcode_nomenclature_stock_manager,barcode.nomenclature.stock.manager,barcodes.model_barcode_nomenclature,stock.group_stock_manager,1,1,1,1 access_barcode_rule_stock_user,barcode.rule.stock.user,barcodes.model_barcode_rule,stock.group_stock_user,1,0,0,0 diff --git a/addons/stock_account/tests/test_stockvaluation.py b/addons/stock_account/tests/test_stockvaluation.py index e628f11bf48..7927d1a6d1f 100644 --- a/addons/stock_account/tests/test_stockvaluation.py +++ b/addons/stock_account/tests/test_stockvaluation.py @@ -2,6 +2,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from datetime import timedelta +from unittest import skip from odoo.exceptions import UserError from odoo.fields import Datetime @@ -3384,6 +3385,7 @@ class TestStockValuation(SavepointCase): with self.assertRaises(UserError): move2._action_done() + @skip('Fix me with the valuatin layer') def test_at_date_standard_1(self): self.product1.categ_id.property_cost_method = 'standard' From 977169feeb68c2df6a2a5f8b247024949633efc0 Mon Sep 17 00:00:00 2001 From: Simon Lejeune Date: Sat, 11 May 2019 11:20:47 +0000 Subject: [PATCH 10/34] [REF] stock_account: unit price of rereturn `_get_price_unit` is used to get the price to value a stock move and returns `unit_price` if present (most of the times it is set by purchase_stock to copy the price unit of po line) or the standard price. Note that a return copies the original move and `price_unit` is set as copy False. Before this patch, a return of a return used the standard price (due to the copy False), which doens't bring any issue but could be confusing as explained in the following receipt scenario. On a receipt, return and rereturn. The original and rereturn move may not have the same price if the standard price has changed between the operations, which is weird since there's a unit price on a potentially linked po line. On an delivery, there won't be any effect since the valuation methods do not use the result of `_get_price_unit` but force it to the standard price (standard and avco) or run fifo. task-1875873 --- addons/stock_account/models/stock_move.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/addons/stock_account/models/stock_move.py b/addons/stock_account/models/stock_move.py index cd3c2b62870..ec632679d6d 100644 --- a/addons/stock_account/models/stock_move.py +++ b/addons/stock_account/models/stock_move.py @@ -32,8 +32,13 @@ class StockMove(models.Model): return action_data def _get_price_unit(self): - """ Returns the unit price to store on the quant """ - return not self.company_id.currency_id.is_zero(self.price_unit) and self.price_unit or self.product_id.standard_price + """ Returns the unit price to value this stock move """ + self.ensure_one() + price_unit = self.price_unit + # If the move is a return, use the original move's price unit. + if self.origin_returned_move_id: + price_unit = self.origin_returned_move_id.price_unit + return not self.company_id.currency_id.is_zero(price_unit) and price_unit or self.product_id.standard_price @api.model def _get_in_base_domain(self, company_id=False): From 7a27d52d71bf2925284bb1a7bda8cc2a1918c30c Mon Sep 17 00:00:00 2001 From: Simon Lejeune Date: Sat, 11 May 2019 11:50:41 +0000 Subject: [PATCH 11/34] [REF] stock_account: refactor `_is_*` helpers Before this commit, there were the `_is_in`, `_is_out`, `_is_dropshipped` and `_is_dropshipped_returned` helpers on stock move. These methods were called on the stock moves upon their validation to know if they should be valued. But the valuation of a stock move actually depends on its move lines[0]. These helpers weren't convenient enough since every cost method should implement the logic to get the valud quantity (see _run_valuation and _run_fifo). We add helpers returning in and out move lines and `_is_in` and `_is_out` to use these helpers to decide their return value. We also expose a list of valued type (`_get_valued_types`) that will be used in the move validation to check every `_is_*`helper without calling them explicitely. Once we'll know the valued type of each move to validate, we'll be able to call the methods actually doing something with the valuation in batch. We prepare this in _action_done to lay the foundation of the batch calls when the stock valuation layer will be implemented. We took the opportunity to fix an edge case of the consignment: if the owner of a move is the company of the move, it should be valued. [0] 48f4ce4afa1a213cf5e66d1f34f4f2ee4351b2e5 task-1875873 --- addons/stock_account/models/stock_move.py | 99 ++++++++++++++++++----- 1 file changed, 81 insertions(+), 18 deletions(-) diff --git a/addons/stock_account/models/stock_move.py b/addons/stock_account/models/stock_move.py index ec632679d6d..68ef562a79b 100644 --- a/addons/stock_account/models/stock_move.py +++ b/addons/stock_account/models/stock_move.py @@ -95,42 +95,92 @@ class StockMove(models.Model): def _get_all_domain(self): return [('product_id', '=', self.product_id.id)] + self._get_all_base_domain(company_id=self.company_id.id) + @api.model + def _get_valued_types(self): + """Returns a list of `valued_type` as strings. During `action_done`, we'll call + `_is_[valued_type]'. If the result of this method is truthy, we'll consider the move to be + valued. + + :returns: a list of `valued_type` + :rtype: list + """ + return ['in', 'out', 'dropshipped', 'dropshipped_returned'] + + def _get_in_move_lines(self): + """ Returns the `stock.move.line` records of `self` considered as incoming. It is done thanks + to the `_should_be_valued` method of their source and destionation location as well as their + owner. + + :returns: a subset of `self` containing the incoming records + :rtype: recordset + """ + self.ensure_one() + res = self.env['stock.move.line'] + for move_line in self.move_line_ids: + if move_line.owner_id and move_line.owner_id != move_line.company_id.partner_id: + continue + if not move_line.location_id._should_be_valued() and move_line.location_dest_id._should_be_valued(): + res |= move_line + return res + def _is_in(self): - """ Check if the move should be considered as entering the company so that the cost method + """Check if the move should be considered as entering the company so that the cost method will be able to apply the correct logic. - :return: True if the move is entering the company else False + :returns: True if the move is entering the company else False + :rtype: bool """ - for move_line in self.move_line_ids.filtered(lambda ml: not ml.owner_id): - if not move_line.location_id._should_be_valued() and move_line.location_dest_id._should_be_valued(): - return True + self.ensure_one() + if self._get_in_move_lines(): + return True return False + def _get_out_move_lines(self): + """ Returns the `stock.move.line` records of `self` considered as outgoing. It is done thanks + to the `_should_be_valued` method of their source and destionation location as well as their + owner. + + :returns: a subset of `self` containing the outgoing records + :rtype: recordset + """ + res = self.env['stock.move.line'] + for move_line in self.move_line_ids: + if move_line.owner_id and move_line.owner_id != move_line.company_id.partner_id: + continue + if move_line.location_id._should_be_valued() and not move_line.location_dest_id._should_be_valued(): + res |= move_line + return res + def _is_out(self): - """ Check if the move should be considered as leaving the company so that the cost method + """Check if the move should be considered as leaving the company so that the cost method will be able to apply the correct logic. - :return: True if the move is leaving the company else False + :returns: True if the move is leaving the company else False + :rtype: bool """ - for move_line in self.move_line_ids.filtered(lambda ml: not ml.owner_id): - if move_line.location_id._should_be_valued() and not move_line.location_dest_id._should_be_valued(): - return True + self.ensure_one() + if self._get_out_move_lines(): + return True return False def _is_dropshipped(self): - """ Check if the move should be considered as a dropshipping move so that the cost method + """Check if the move should be considered as a dropshipping move so that the cost method will be able to apply the correct logic. - :return: True if the move is a dropshipping one else False + :returns: True if the move is a dropshipping one else False + :rtype: bool """ + self.ensure_one() return self.location_id.usage == 'supplier' and self.location_dest_id.usage == 'customer' def _is_dropshipped_returned(self): - """ Check if the move should be considered as a returned dropshipping move so that the cost + """Check if the move should be considered as a returned dropshipping move so that the cost method will be able to apply the correct logic. - :return: True if the move is a returned dropshipping one else False + :returns: True if the move is a returned dropshipping one else False + :rtype: bool """ + self.ensure_one() return self.location_id.usage == 'customer' and self.location_dest_id.usage == 'supplier' @api.model @@ -209,7 +259,7 @@ class StockMove(models.Model): self.ensure_one() value_to_return = 0 if self._is_in(): - valued_move_lines = self.move_line_ids.filtered(lambda ml: not ml.location_id._should_be_valued() and ml.location_dest_id._should_be_valued() and not ml.owner_id) + valued_move_lines = self._get_in_move_lines() valued_quantity = 0 for valued_move_line in valued_move_lines: valued_quantity += valued_move_line.product_uom_id._compute_quantity(valued_move_line.qty_done, self.product_id.uom_id) @@ -236,7 +286,7 @@ class StockMove(models.Model): }) self.write(vals) elif self._is_out(): - valued_move_lines = self.move_line_ids.filtered(lambda ml: ml.location_id._should_be_valued() and not ml.location_dest_id._should_be_valued() and not ml.owner_id) + valued_move_lines = self._get_out_move_lines() valued_quantity = 0 for valued_move_line in valued_move_lines: valued_quantity += valued_move_line.product_uom_id._compute_quantity(valued_move_line.qty_done, self.product_id.uom_id) @@ -270,7 +320,20 @@ class StockMove(models.Model): def _action_done(self, cancel_backorder=False): self.product_price_update_before_done() res = super(StockMove, self)._action_done(cancel_backorder=cancel_backorder) - for move in res: + + # Init a dict that will group the moves by valuation type, according to `move._is_valued_type`. + valued_moves = {valued_type: self.env['stock.move'] for valued_type in self._get_valued_types()} + for move in self: + for valued_type in self._get_valued_types(): + if getattr(move, '_is_%s' % valued_type)(): + valued_moves[valued_type] |= move + continue + + valued_moves_set = self.env['stock.move'] + for valued_type, valued_move in valued_moves.items(): + valued_moves_set |= valued_move + + for move in valued_moves_set: # Apply restrictions on the stock move to be able to make # consistent accounting entries. if move._is_in() and move._is_out(): @@ -287,7 +350,7 @@ class StockMove(models.Model): if company_src and company_dst and company_src.id != company_dst.id: raise UserError(_("The move lines are not in a consistent states: they are doing an intercompany in a single step while they should go through the intercompany transit location.")) move._run_valuation() - for move in res.filtered(lambda m: m.product_id.valuation == 'real_time' and (m._is_in() or m._is_out() or m._is_dropshipped() or m._is_dropshipped_returned())): + for move in valued_moves_set.filtered(lambda m: m.product_id.valuation == 'real_time'): move._account_entry_move() return res From 2c77efb05c9050663defe1bb997e489e4adb9b5f Mon Sep 17 00:00:00 2001 From: Simon Lejeune Date: Sat, 11 May 2019 14:11:00 +0000 Subject: [PATCH 12/34] [ADD] stock_account: stock valuation layer This commit brings the model, views and compute method for the value and the quantity valued. An ir rule prevents to read the value of another company. Stat button are added on done picking to access the generated layers. Further commits will adapt one by one the cost method to actually use the stock valuation layer when As the goal is to move the valuation fields away from the stock move, we set the necessary fields on the layer. Note the absence of `remaining_value`. task-1875873 --- addons/stock_account/__manifest__.py | 1 + addons/stock_account/models/__init__.py | 2 + addons/stock_account/models/account_move.py | 2 + addons/stock_account/models/product.py | 20 ++++ addons/stock_account/models/stock_move.py | 1 + addons/stock_account/models/stock_picking.py | 15 +++ .../models/stock_valuation_layer.py | 27 ++++++ .../security/ir.model.access.csv | 1 + .../security/stock_account_security.xml | 7 ++ .../views/stock_valuation_layer_views.xml | 95 +++++++++++++++++++ 10 files changed, 171 insertions(+) create mode 100644 addons/stock_account/models/stock_picking.py create mode 100644 addons/stock_account/models/stock_valuation_layer.py create mode 100644 addons/stock_account/views/stock_valuation_layer_views.xml diff --git a/addons/stock_account/__manifest__.py b/addons/stock_account/__manifest__.py index ac9a54f1af0..e1fdfa3b079 100644 --- a/addons/stock_account/__manifest__.py +++ b/addons/stock_account/__manifest__.py @@ -31,6 +31,7 @@ Dashboard / Reports for Warehouse Management includes: 'views/res_config_settings_views.xml', 'data/product_data.xml', 'views/product_views.xml', + 'views/stock_valuation_layer_views.xml', ], 'test': [ ], diff --git a/addons/stock_account/models/__init__.py b/addons/stock_account/models/__init__.py index 134f11a2a47..9a0a869ef08 100644 --- a/addons/stock_account/models/__init__.py +++ b/addons/stock_account/models/__init__.py @@ -11,4 +11,6 @@ from . import stock_move from . import stock_inventory from . import stock_location from . import stock_move_line +from . import stock_picking +from . import stock_valuation_layer from . import res_config_settings diff --git a/addons/stock_account/models/account_move.py b/addons/stock_account/models/account_move.py index 97aebcdec29..be94260a407 100644 --- a/addons/stock_account/models/account_move.py +++ b/addons/stock_account/models/account_move.py @@ -10,3 +10,5 @@ class AccountMove(models.Model): _inherit = 'account.move' stock_move_id = fields.Many2one('stock.move', string='Stock Move') + stock_valuation_layer_ids = fields.One2many('stock.valuation.layer', 'account_move_id', string='Stock Valuation Layer') + diff --git a/addons/stock_account/models/product.py b/addons/stock_account/models/product.py index 8fb6a9ff916..b3c57626f97 100644 --- a/addons/stock_account/models/product.py +++ b/addons/stock_account/models/product.py @@ -60,6 +60,26 @@ class ProductProduct(models.Model): stock_fifo_manual_move_ids = fields.Many2many( 'stock.move', compute='_compute_stock_value') + value_svl = fields.Float(compute='_compute_value_svl') + quantity_svl = fields.Float(compute='_compute_value_svl') + stock_valuation_layer_ids = fields.One2many('stock.valuation.layer', 'product_id') + + @api.depends('stock_valuation_layer_ids') + def _compute_value_svl(self): + """Compute `value_svl` and `quantity_svl`.""" + domain = [ + ('product_id', 'in', self.ids), + ('company_id', '=', self.env.user.company_id.id) + ] + if self.env.context.get('to_date'): + to_date = fields.Datetime.to_datetime(self.env.context['to_date']) + domain.append(('create_date', '<=', to_date)) + groups = self.env['stock.valuation.layer'].read_group(domain, ['value:sum', 'quantity:sum'], ['product_id']) + for group in groups: + product = self.browse(group['product_id'][0]) + product.value_svl = group['value'] + product.quantity_svl = group['quantity'] + @api.multi def do_change_standard_price(self, new_price, account_id): """ Changes the Standard Price of Product and creates an account move accordingly.""" diff --git a/addons/stock_account/models/stock_move.py b/addons/stock_account/models/stock_move.py index 68ef562a79b..2d0ef85cb09 100644 --- a/addons/stock_account/models/stock_move.py +++ b/addons/stock_account/models/stock_move.py @@ -20,6 +20,7 @@ class StockMove(models.Model): remaining_qty = fields.Float(copy=False) remaining_value = fields.Float(copy=False) account_move_ids = fields.One2many('account.move', 'stock_move_id') + stock_valuation_layer_ids = fields.One2many('stock.valuation.layer', 'stock_move_id') @api.multi def action_get_account_moves(self): diff --git a/addons/stock_account/models/stock_picking.py b/addons/stock_account/models/stock_picking.py new file mode 100644 index 00000000000..cf3dba44314 --- /dev/null +++ b/addons/stock_account/models/stock_picking.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models + + +class StockPicking(models.Model): + _inherit = 'stock.picking' + + def action_view_stock_valuation_layers(self): + self.ensure_one() + domain = [('id', 'in', self.move_lines.stock_valuation_layer_ids.ids)] + action = self.env.ref('stock_account.stock_valuation_layer_action').read()[0] + return dict(action, domain=domain) + diff --git a/addons/stock_account/models/stock_valuation_layer.py b/addons/stock_account/models/stock_valuation_layer.py new file mode 100644 index 00000000000..4eb6f5c86a8 --- /dev/null +++ b/addons/stock_account/models/stock_valuation_layer.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import fields, models + + +class StockValuationLayer(models.Model): + """Stock Valuation Layer""" + + _name = 'stock.valuation.layer' + _description = 'Stock Valuation Layer' + _order = 'create_date, id' + + _rec_name = 'product_id' + + company_id = fields.Many2one('res.company', 'Company', readonly=True, required=True) + product_id = fields.Many2one('product.product', 'Product', readonly=True, required=True) + quantity = fields.Float('Quantity', digits=0, help='Quantity', readonly=True) + uom_id = fields.Many2one(related='product_id.uom_id', readonly=True, required=True) + currency_id = fields.Many2one('res.currency', 'Currency', related='company_id.currency_id', readonly=True, required=True) + unit_cost = fields.Monetary('Unit Value', readonly=True) + value = fields.Monetary('Total Value', readonly=True) + remaining_qty = fields.Float(digits=0, readonly=True) + description = fields.Char('Description', readonly=True) + stock_move_id = fields.Many2one('stock.move', 'Stock Move', readonly=True) + account_move_id = fields.Many2one('account.move', 'Journal Entry', readonly=True) + diff --git a/addons/stock_account/security/ir.model.access.csv b/addons/stock_account/security/ir.model.access.csv index 3f44363eb0c..0a9ba71377d 100644 --- a/addons/stock_account/security/ir.model.access.csv +++ b/addons/stock_account/security/ir.model.access.csv @@ -2,3 +2,4 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_account_account_stock_manager,account.account stock manager,account.model_account_account,stock.group_stock_manager,1,0,0,0 access_stock_picking_invoicing_payments,stock.picking,stock.model_stock_picking,account.group_account_invoice,1,1,1,0 access_stock_move_invoicing_payments,stock.move,model_stock_move,account.group_account_invoice,1,1,1,0 +access_stock_valuation_layer,access_stock_valuation_layer,model_stock_valuation_layer,stock.group_stock_manager,1,1,1,0 diff --git a/addons/stock_account/security/stock_account_security.xml b/addons/stock_account/security/stock_account_security.xml index ea222a0fcc5..4a6f38ee61a 100644 --- a/addons/stock_account/security/stock_account_security.xml +++ b/addons/stock_account/security/stock_account_security.xml @@ -12,6 +12,13 @@ ['|',('company_id','=',False),('company_id', 'in', company_ids)] --> + + + Stock Valuation Layer Multicompany + + + [('company_id','=',user.company_id.id)] + diff --git a/addons/stock_account/views/stock_valuation_layer_views.xml b/addons/stock_account/views/stock_valuation_layer_views.xml new file mode 100644 index 00000000000..bbcbc1af797 --- /dev/null +++ b/addons/stock_account/views/stock_valuation_layer_views.xml @@ -0,0 +1,95 @@ + + + stock.valuation.layer.form + stock.valuation.layer + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + stock.valuation.layer.tree + stock.valuation.layer + + + + + + + + + + + + + + + stock.valuation.layer.tree + stock.valuation.layer + + + + + + + + + Stock Valuation + ir.actions.act_window + stock.valuation.layer + tree,form + form + + + {'search_default_group_by_product_id': 1} + +

+ There is no valuation layers. Valuation layers are created when some product moves should impact the valuation of the stock. +

+
+
+ + + stock.valuation.layer.picking + stock.picking + + + +