From 6fd05759e7f77f010dbd3fb8a9960128f4ee9fa1 Mon Sep 17 00:00:00 2001 From: Simon Lejeune Date: Mon, 20 May 2019 08:22:12 +0000 Subject: [PATCH] [REF] stock_account: remove old valuation code The new code was sitting next to the old one, now that everything is adapted (minus the cost method changes), we can remove the old one. We also adapt the valuation wizard to use the stock valuation layer action, now that the others are removed. task-1875873 --- addons/stock_account/models/__init__.py | 1 - .../stock_account/models/procurement_group.py | 14 - addons/stock_account/models/product.py | 227 +------------- addons/stock_account/models/stock_move.py | 276 +----------------- .../stock_account/models/stock_move_line.py | 81 +---- .../tests/test_stockvaluation.py | 35 ++- .../tests/test_stockvaluationlayer.py | 14 +- addons/stock_account/views/product_views.xml | 18 -- .../views/stock_account_views.xml | 48 --- .../wizard/stock_quantity_history.py | 21 +- 10 files changed, 39 insertions(+), 696 deletions(-) delete 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 9a0a869ef08..a47ce5720ee 100644 --- a/addons/stock_account/models/__init__.py +++ b/addons/stock_account/models/__init__.py @@ -5,7 +5,6 @@ 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_move from . import stock_inventory diff --git a/addons/stock_account/models/procurement_group.py b/addons/stock_account/models/procurement_group.py deleted file mode 100644 index 2372c13bd57..00000000000 --- a/addons/stock_account/models/procurement_group.py +++ /dev/null @@ -1,14 +0,0 @@ -# -*- 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/product.py b/addons/stock_account/models/product.py index 84271711164..c14e18c5672 100644 --- a/addons/stock_account/models/product.py +++ b/addons/stock_account/models/product.py @@ -32,11 +32,6 @@ class ProductTemplate(models.Model): }) return accounts - @api.multi - def action_open_product_moves(self): - # TODO: remove me in master - pass - @api.multi def get_product_accounts(self, fiscal_pos=None): """ Add the stock journal related to product to the result of super() @@ -50,16 +45,6 @@ class ProductTemplate(models.Model): class ProductProduct(models.Model): _inherit = 'product.product' - stock_value_currency_id = fields.Many2one('res.currency', compute='_compute_stock_value_currency') - stock_value = fields.Float( - 'Value', compute='_compute_stock_value') - qty_at_date = fields.Float( - 'Quantity', compute='_compute_stock_value') - stock_fifo_real_time_aml_ids = fields.Many2many( - 'account.move.line', compute='_compute_stock_value') - 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') @@ -331,202 +316,8 @@ class ProductProduct(models.Model): vacuum_svl.stock_move_id.with_context(svl_id=vacuum_svl.id, force_valuation_amount=vacuum_svl.value, forced_quantity=vacuum_svl.quantity, forced_ref=vacuum_svl.description)._account_entry_move() # ------------------------------------------------------------------------- - # Miscellaneous + # Anglo saxon helpers # ------------------------------------------------------------------------- - @api.multi - def do_change_standard_price(self, new_price, account_id): - """ Changes the Standard Price of Product and creates an account move accordingly.""" - AccountMove = self.env['account.move'] - - quant_locs = self.env['stock.quant'].sudo().read_group([('product_id', 'in', self.ids)], ['location_id'], ['location_id']) - quant_loc_ids = [loc['location_id'][0] for loc in quant_locs] - locations = self.env['stock.location'].search([('usage', '=', 'internal'), ('company_id', '=', self.env.company.id), ('id', 'in', quant_loc_ids)]) - - product_accounts = {product.id: product.product_tmpl_id.get_product_accounts() for product in self} - - for location in locations: - for product in self.with_context(location=location.id, compute_child=False).filtered(lambda r: r.valuation == 'real_time'): - diff = product.standard_price - new_price - if float_is_zero(diff, precision_rounding=product.currency_id.rounding): - raise UserError(_("No difference between the standard price and the new price.")) - if not product_accounts[product.id].get('stock_valuation', False): - raise UserError(_('You don\'t have any stock valuation account defined on your product category. You must define one before processing this operation.')) - qty_available = product.qty_available - if qty_available: - # Accounting Entries - if diff * qty_available > 0: - debit_account_id = account_id - credit_account_id = product_accounts[product.id]['stock_valuation'].id - else: - debit_account_id = product_accounts[product.id]['stock_valuation'].id - credit_account_id = account_id - - move_vals = { - 'journal_id': product_accounts[product.id]['stock_journal'].id, - 'company_id': location.company_id.id, - 'ref': product.default_code, - 'line_ids': [(0, 0, { - 'name': _('%s changed cost from %s to %s - %s') % (self.env.user.name, product.standard_price, new_price, product.display_name), - 'account_id': debit_account_id, - 'debit': abs(diff * qty_available), - 'credit': 0, - 'product_id': product.id, - }), (0, 0, { - 'name': _('%s changed cost from %s to %s - %s') % (self.env.user.name, product.standard_price, new_price, product.display_name), - 'account_id': credit_account_id, - 'debit': 0, - 'credit': abs(diff * qty_available), - 'product_id': product.id, - })], - } - move = AccountMove.create(move_vals) - move.post() - - self.write({'standard_price': new_price}) - return True - - def _get_fifo_candidates_in_move(self): - """ Find IN moves that can be used to value OUT moves. - """ - return self._get_fifo_candidates_in_move_with_company() - - def _get_fifo_candidates_in_move_with_company(self, move_company_id=False): - self.ensure_one() - domain = [('product_id', '=', self.id), ('remaining_qty', '>', 0.0)] + self.env['stock.move']._get_in_base_domain(move_company_id) - candidates = self.env['stock.move'].search(domain, order='date, id') - return candidates - - def _sum_remaining_values(self): - StockMove = self.env['stock.move'] - domain = [('product_id', '=', self.id)] + StockMove._get_all_base_domain() - moves = StockMove.search(domain) - return sum(moves.mapped('remaining_value')), moves - - @api.multi - def _compute_stock_value_currency(self): - currency_id = self.env.company.currency_id - for product in self: - product.stock_value_currency_id = currency_id - - @api.multi - @api.depends('stock_move_ids.product_qty', 'stock_move_ids.state', 'stock_move_ids.remaining_value', 'product_tmpl_id.cost_method', 'product_tmpl_id.standard_price', 'product_tmpl_id.categ_id.property_valuation') - def _compute_stock_value(self): - StockMove = self.env['stock.move'] - to_date = self.env.context.get('to_date') - - real_time_product_ids = [product.id for product in self if product.product_tmpl_id.valuation == 'real_time'] - if real_time_product_ids: - self.env['account.move.line'].check_access_rights('read') - fifo_automated_values = {} - query = """SELECT aml.product_id, aml.account_id, sum(aml.debit) - sum(aml.credit), sum(quantity), array_agg(aml.id) - FROM account_move_line AS aml - WHERE aml.product_id IN %%s AND aml.company_id=%%s %s - GROUP BY aml.product_id, aml.account_id""" - params = (tuple(real_time_product_ids), self.env.company.id) - if to_date: - query = query % ('AND aml.date <= %s',) - params = params + (to_date,) - else: - query = query % ('',) - self.env.cr.execute(query, params=params) - - res = self.env.cr.fetchall() - for row in res: - fifo_automated_values[(row[0], row[1])] = (row[2], row[3], list(row[4])) - - product_values = {product.id: 0 for product in self} - product_move_ids = {product.id: [] for product in self} - - if to_date: - domain = [('product_id', 'in', self.ids), ('date', '<=', to_date)] + StockMove._get_all_base_domain() - value_field_name = 'value' - else: - domain = [('product_id', 'in', self.ids)] + StockMove._get_all_base_domain() - value_field_name = 'remaining_value' - - StockMove.check_access_rights('read') - query = StockMove._where_calc(domain) - StockMove._apply_ir_rules(query, 'read') - from_clause, where_clause, params = query.get_sql() - query_str = """ - SELECT stock_move.product_id, SUM(COALESCE(stock_move.{}, 0.0)), ARRAY_AGG(stock_move.id) - FROM {} - WHERE {} - GROUP BY stock_move.product_id - """.format(value_field_name, from_clause, where_clause) - self.env.cr.execute(query_str, params) - for product_id, value, move_ids in self.env.cr.fetchall(): - product_values[product_id] = value - product_move_ids[product_id] = move_ids - - for product in self: - if product.cost_method in ['standard', 'average']: - qty_available = product.with_context(company_owned=True, owner_id=False).qty_available - price_used = product.standard_price - if to_date: - price_used = product.get_history_price( - self.env.company.id, - date=to_date, - ) - product.stock_value = price_used * qty_available - product.qty_at_date = qty_available - elif product.cost_method == 'fifo': - if to_date: - if product.product_tmpl_id.valuation == 'manual_periodic': - product.stock_value = product_values[product.id] - product.qty_at_date = product.with_context(company_owned=True, owner_id=False).qty_available - product.stock_fifo_manual_move_ids = StockMove.browse(product_move_ids[product.id]) - elif product.product_tmpl_id.valuation == 'real_time': - valuation_account_id = product.categ_id.property_stock_valuation_account_id.id - value, quantity, aml_ids = fifo_automated_values.get((product.id, valuation_account_id)) or (0, 0, []) - product.stock_value = value - product.qty_at_date = quantity - product.stock_fifo_real_time_aml_ids = self.env['account.move.line'].browse(aml_ids) - else: - product.stock_value = product_values[product.id] - product.qty_at_date = product.with_context(company_owned=True, owner_id=False).qty_available - if product.product_tmpl_id.valuation == 'manual_periodic': - product.stock_fifo_manual_move_ids = StockMove.browse(product_move_ids[product.id]) - elif product.product_tmpl_id.valuation == 'real_time': - valuation_account_id = product.categ_id.property_stock_valuation_account_id.id - value, quantity, aml_ids = fifo_automated_values.get((product.id, valuation_account_id)) or (0, 0, []) - product.stock_fifo_real_time_aml_ids = self.env['account.move.line'].browse(aml_ids) - - def action_valuation_at_date_details(self): - """ Returns an action with either a list view of all the valued stock moves of `self` if the - valuation is set as manual or a list view of all the account move lines if the valuation is - set as automated. - """ - self.ensure_one() - to_date = self.env.context.get('to_date') - ctx = self.env.context.copy() - ctx.pop('group_by', None) - action = { - 'name': _('Valuation at date'), - 'type': 'ir.actions.act_window', - 'view_type': 'form', - 'view_mode': 'tree,form', - 'context': ctx, - } - if self.valuation == 'real_time': - action['res_model'] = 'account.move.line' - action['domain'] = [('id', 'in', self.with_context(to_date=to_date).stock_fifo_real_time_aml_ids.ids)] - tree_view_ref = self.env.ref('stock_account.view_stock_account_aml') - form_view_ref = self.env.ref('account.view_move_line_form') - action['views'] = [(tree_view_ref.id, 'tree'), (form_view_ref.id, 'form')] - else: - action['res_model'] = 'stock.move' - action['domain'] = [('id', 'in', self.with_context(to_date=to_date).stock_fifo_manual_move_ids.ids)] - tree_view_ref = self.env.ref('stock_account.view_move_tree_valuation_at_date') - form_view_ref = self.env.ref('stock.view_move_form') - action['views'] = [(tree_view_ref.id, 'tree'), (form_view_ref.id, 'form')] - return action - - @api.multi - def action_open_product_moves(self): - #TODO: remove me in master - pass - @api.model def _anglo_saxon_sale_move_lines(self, name, product, uom, qty, price_unit, currency=False, amount_currency=False, fiscal_position=False, account_analytic=False, analytic_tags=False): """Prepare dicts describing new journal COGS journal items for a product sale. @@ -661,14 +452,6 @@ class ProductCategory(models.Model): input_and_output_accounts = category.property_stock_account_input_categ_id | category.property_stock_account_output_categ_id if valuation_account and valuation_account in input_and_output_accounts: raise ValidationError(_('The Stock Input and/or Output accounts cannot be the same than the Stock Valuation account.')) - @api.multi - def write(self, vals): - # When going from FIFO to AVCO or to standard, we update the standard price with the - # average value in stock. - cost_method = vals.get('property_cost_method') - if cost_method and cost_method in ['average', 'standard']: - self._update_standard_price() - return super(ProductCategory, self).write(vals) @api.onchange('property_cost_method') def onchange_property_valuation(self): @@ -682,11 +465,3 @@ class ProductCategory(models.Model): } } - def _update_standard_price(self): - updated_categories = self.filtered(lambda x: x.property_cost_method == 'fifo') - templates = self.env['product.template'].search([('categ_id', 'in', updated_categories.ids)]) - for t in templates: - valuation = sum([variant._sum_remaining_values()[0] for variant in t.product_variant_ids]) - qty_available = t.with_context(company_owned=True).qty_available - if qty_available: - t.standard_price = valuation / qty_available diff --git a/addons/stock_account/models/stock_move.py b/addons/stock_account/models/stock_move.py index 89c69562a36..23f3e278a0b 100644 --- a/addons/stock_account/models/stock_move.py +++ b/addons/stock_account/models/stock_move.py @@ -16,9 +16,6 @@ class StockMove(models.Model): to_refund = fields.Boolean(string="Update quantities on SO/PO", copy=False, help='Trigger a decrease of the delivered/received quantity in the associated Sale Order/Purchase Order') - value = fields.Float(copy=False) - 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') @@ -41,61 +38,6 @@ class StockMove(models.Model): 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): - # Domain: - # - state is done - # - coming from a location without company, or an inventory location within the same company - # - going to a location within the same company - domain = [ - ('state', '=', 'done'), - '&', - '|', - ('location_id.company_id', '=', False), - '&', - ('location_id.usage', 'in', ['inventory', 'production']), - ('location_id.company_id', '=', company_id or self.env.company.id), - ('location_dest_id.company_id', '=', company_id or self.env.company.id), - ] - return domain - - @api.model - def _get_all_base_domain(self, company_id=False): - # Domain: - # - state is done - # Then, either 'in' or 'out' moves. - # 'in' moves: - # - coming from a location without company, or an inventory location within the same company - # - going to a location within the same company - # 'out' moves: - # - coming from to a location within the same company - # - going to a location without company, or an inventory location within the same company - domain = [ - ('state', '=', 'done'), - '|', - '&', - '|', - ('location_id.company_id', '=', False), - '&', - ('location_id.usage', 'in', ['inventory', 'production']), - ('location_id.company_id', '=', company_id or self.env.company.id), - ('location_dest_id.company_id', '=', company_id or self.env.company.id), - '&', - ('location_id.company_id', '=', company_id or self.env.company.id), - '|', - ('location_dest_id.company_id', '=', False), - '&', - ('location_dest_id.usage', '=', 'inventory'), - ('location_dest_id.company_id', '=', company_id or self.env.company.id), - ] - return domain - - def _get_in_domain(self): - return [('product_id', '=', self.product_id.id)] + self._get_in_base_domain(company_id=self.company_id.id) - - 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 @@ -287,140 +229,6 @@ class StockMove(models.Model): """ return self._create_dropshipped_svl(forced_quantity=forced_quantity) - @api.model - def _run_fifo(self, move, quantity=None): - """ Value `move` according to the FIFO rule, meaning we consume the - oldest receipt first. Candidates receipts are marked consumed or free - thanks to their `remaining_qty` and `remaining_value` fields. - By definition, `move` should be an outgoing stock move. - - :param quantity: quantity to value instead of `move.product_qty` - :returns: valued amount in absolute - """ - move.ensure_one() - - # Deal with possible move lines that do not impact the valuation. - valued_move_lines = move.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_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, move.product_id.uom_id) - - # Find back incoming stock moves (called candidates here) to value this move. - qty_to_take_on_candidates = quantity or valued_quantity - candidates = move.product_id._get_fifo_candidates_in_move_with_company(move.company_id.id) - new_standard_price = 0 - tmp_value = 0 # to accumulate the value taken on the candidates - for candidate in candidates: - new_standard_price = candidate.price_unit - if candidate.remaining_qty <= qty_to_take_on_candidates: - qty_taken_on_candidate = candidate.remaining_qty - else: - qty_taken_on_candidate = qty_to_take_on_candidates - - # As applying a landed cost do not update the unit price, naivelly doing - # something like qty_taken_on_candidate * candidate.price_unit won't make - # the additional value brought by the landed cost go away. - candidate_price_unit = candidate.remaining_value / candidate.remaining_qty - value_taken_on_candidate = qty_taken_on_candidate * candidate_price_unit - candidate_vals = { - 'remaining_qty': candidate.remaining_qty - qty_taken_on_candidate, - 'remaining_value': candidate.remaining_value - value_taken_on_candidate, - } - candidate.write(candidate_vals) - - qty_to_take_on_candidates -= qty_taken_on_candidate - tmp_value += value_taken_on_candidate - if qty_to_take_on_candidates == 0: - break - - # Update the standard price with the price of the last used candidate, if any. - if new_standard_price and move.product_id.cost_method == 'fifo': - move.product_id.sudo().with_context(force_company=move.company_id.id) \ - .standard_price = new_standard_price - - # If there's still quantity to value but we're out of candidates, we fall in the - # negative stock use case. We chose to value the out move at the price of the - # last out and a correction entry will be made once `_fifo_vacuum` is called. - if qty_to_take_on_candidates == 0: - move.write({ - 'value': -tmp_value if not quantity else move.value or -tmp_value, # outgoing move are valued negatively - 'price_unit': -tmp_value / (move.product_qty or quantity), - }) - elif qty_to_take_on_candidates > 0: - last_fifo_price = new_standard_price or move.product_id.standard_price - negative_stock_value = last_fifo_price * -qty_to_take_on_candidates - tmp_value += abs(negative_stock_value) - vals = { - 'remaining_qty': move.remaining_qty + -qty_to_take_on_candidates, - 'remaining_value': move.remaining_value + negative_stock_value, - 'value': -tmp_value, - 'price_unit': -1 * last_fifo_price, - } - move.write(vals) - return tmp_value - - def _run_valuation(self, quantity=None): - self.ensure_one() - value_to_return = 0 - if self._is_in(): - 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) - - # Note: we always compute the fifo `remaining_value` and `remaining_qty` fields no - # matter which cost method is set, to ease the switching of cost method. - vals = {} - price_unit = self._get_price_unit() - value = price_unit * (quantity or valued_quantity) - value_to_return = value if quantity is None or not self.value else self.value - vals = { - 'price_unit': price_unit, - 'value': value_to_return, - 'remaining_value': value if quantity is None else self.remaining_value + value, - } - vals['remaining_qty'] = valued_quantity if quantity is None else self.remaining_qty + quantity - - if self.product_id.cost_method == 'standard': - value = self.product_id.standard_price * (quantity or valued_quantity) - value_to_return = value if quantity is None or not self.value else self.value - vals.update({ - 'price_unit': self.product_id.standard_price, - 'value': value_to_return, - }) - self.write(vals) - elif self._is_out(): - 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) - self.env['stock.move']._run_fifo(self, quantity=quantity) - if self.product_id.cost_method in ['standard', 'average']: - curr_rounding = self.company_id.currency_id.rounding - value = -float_round(self.product_id.standard_price * (valued_quantity if quantity is None else quantity), precision_rounding=curr_rounding) - value_to_return = value if quantity is None else self.value + value - self.write({ - 'value': value_to_return, - 'price_unit': value / valued_quantity, - }) - elif self._is_dropshipped() or self._is_dropshipped_returned(): - curr_rounding = self.company_id.currency_id.rounding - if self.product_id.cost_method in ['fifo']: - price_unit = self._get_price_unit() - # see test_dropship_fifo_perpetual_anglosaxon_ordered - self.product_id.standard_price = price_unit - else: - price_unit = self.product_id.standard_price - value = float_round(self.product_qty * price_unit, precision_rounding=curr_rounding) - value_to_return = value if self._is_dropshipped() else -value - # In move have a positive value, out move have a negative value, let's arbitrary say - # dropship are positive. - self.write({ - 'value': value_to_return, - 'price_unit': price_unit if self._is_dropshipped() else -price_unit, - }) - return value_to_return - def _action_done(self, cancel_backorder=False): # 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()} @@ -504,88 +312,6 @@ class StockMove(models.Model): move.product_id.with_context(force_company=move.company_id.id).sudo().write({'standard_price': new_std_price}) std_price_update[move.company_id.id, move.product_id.id] = new_std_price - @api.model - def _fifo_vacuum(self): - """ Every moves that need to be fixed are identifiable by having a negative `remaining_qty`. - """ - for move in self.filtered(lambda m: (m._is_in() or m._is_out()) and m.remaining_qty < 0): - domain = [ - ('remaining_qty', '>', 0), - '|', - ('date', '>', move.date), - '&', - ('date', '=', move.date), - ('id', '>', move.id) - ] - domain += move._get_in_domain() - candidates = self.search(domain, order='date, id') - if not candidates: - continue - qty_to_take_on_candidates = abs(move.remaining_qty) - qty_taken_on_candidates = 0 - tmp_value = 0 - for candidate in candidates: - if candidate.remaining_qty <= qty_to_take_on_candidates: - qty_taken_on_candidate = candidate.remaining_qty - else: - qty_taken_on_candidate = qty_to_take_on_candidates - qty_taken_on_candidates += qty_taken_on_candidate - - value_taken_on_candidate = qty_taken_on_candidate * candidate.price_unit - candidate_vals = { - 'remaining_qty': candidate.remaining_qty - qty_taken_on_candidate, - 'remaining_value': candidate.remaining_value - value_taken_on_candidate, - } - candidate.write(candidate_vals) - - qty_to_take_on_candidates -= qty_taken_on_candidate - tmp_value += value_taken_on_candidate - if qty_to_take_on_candidates == 0: - break - - - # When working with `price_unit`, beware that out move are negative. - move_price_unit = move.price_unit if move._is_out() else -1 * move.price_unit - # Get the estimated value we will correct. - remaining_value_before_vacuum = qty_taken_on_candidates * move_price_unit - new_remaining_qty = move.remaining_qty + qty_taken_on_candidates - new_remaining_value = new_remaining_qty * abs(move.price_unit) - - corrected_value = remaining_value_before_vacuum + tmp_value - move.write({ - 'remaining_value': new_remaining_value, - 'remaining_qty': new_remaining_qty, - 'value': move.value - corrected_value, - }) - - if move.product_id.valuation == 'real_time': - # If `corrected_value` is 0, absolutely do *not* call `_account_entry_move`. We - # force the amount in the context, but in the case it is 0 it'll create an entry - # for the entire cost of the move. This case happens when the candidates moves - # entirely compensate the problematic move. - if move.company_id.currency_id.is_zero(corrected_value): - continue - - if move._is_in(): - # If we just compensated an IN move that has a negative remaining - # quantity, it means the move has returned more items than it received. - # The correction should behave as a return too. As `_account_entry_move` - # will post the natural values for an IN move (credit IN account, debit - # OUT one), we inverse the sign to create the correct entries. - move.with_context(force_valuation_amount=-corrected_value, forced_quantity=0)._account_entry_move() - else: - move.with_context(force_valuation_amount=corrected_value, forced_quantity=0)._account_entry_move() - - @api.model - def _run_fifo_vacuum(self): - # Call `_fifo_vacuum` on concerned moves - fifo_valued_products = self.env['product.product'] - fifo_valued_categories = self.env['product.category'].search([('property_cost_method', '=', 'fifo')]) - fifo_valued_products |= self.env['product.product'].search([('categ_id', 'child_of', fifo_valued_categories.ids)]) - moves_to_vacuum = self.search( - [('product_id', 'in', fifo_valued_products.ids), ('remaining_qty', '<', 0)] + self._get_all_base_domain()) - moves_to_vacuum._fifo_vacuum() - @api.multi def _get_accounting_data_for_valuation(self): """ Return the accounts and journal to use to post Journal Entries for @@ -726,7 +452,7 @@ class StockMove(models.Model): if not ref and self.inventory_id: ref = self.inventory_id.name - move_lines = self.with_context(forced_ref=ref)._prepare_account_move_line(quantity, abs(self.value), credit_account_id, debit_account_id) + move_lines = self.with_context(forced_ref=ref)._prepare_account_move_line(quantity, 0, credit_account_id, debit_account_id) if move_lines: date = self._context.get('force_period_date', fields.Date.context_today(self)) new_account_move = AccountMove.sudo().create({ diff --git a/addons/stock_account/models/stock_move_line.py b/addons/stock_account/models/stock_move_line.py index 4cdee59c77b..390d0276017 100644 --- a/addons/stock_account/models/stock_move_line.py +++ b/addons/stock_account/models/stock_move_line.py @@ -14,39 +14,19 @@ class StockMoveLine(models.Model): @api.model_create_multi def create(self, vals_list): move_lines = super(StockMoveLine, self).create(vals_list) - if self.env.context.get('svl'): - for move_line in move_lines: - if move_line.state != 'done': - continue - move = move_line.move_id - rounding = move.product_id.uom_id.rounding - diff = move_line.qty_done - if float_is_zero(diff, precision_rounding=rounding): - continue - self._create_correction_svl(move, diff) - return move_lines - -# for line in move_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() + for move_line in move_lines: + if move_line.state != 'done': + continue + move = move_line.move_id + rounding = move.product_id.uom_id.rounding + diff = move_line.qty_done + if float_is_zero(diff, precision_rounding=rounding): + continue + self._create_correction_svl(move, diff) return move_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 and self.env.context.get('svl'): + if 'qty_done' in vals: for move_line in self: if move_line.state != 'done': continue @@ -56,47 +36,6 @@ class StockMoveLine(models.Model): if float_is_zero(diff, precision_rounding=rounding): continue self._create_correction_svl(move, diff) - elif '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) # ------------------------------------------------------------------------- diff --git a/addons/stock_account/tests/test_stockvaluation.py b/addons/stock_account/tests/test_stockvaluation.py index 66a695fc956..74f340b9706 100644 --- a/addons/stock_account/tests/test_stockvaluation.py +++ b/addons/stock_account/tests/test_stockvaluation.py @@ -243,7 +243,7 @@ class TestStockValuation(SavepointCase): # Increase received quantity of move1 from 10 to 12, it should create # a new stock layer at the top of the queue. # --------------------------------------------------------------------- - move1.with_context(svl=True).quantity_done = 12 + move1.quantity_done = 12 # stock_account values for move3 self.assertEqual(move1.stock_valuation_layer_ids[-1].unit_cost, 10.0) @@ -391,7 +391,7 @@ class TestStockValuation(SavepointCase): # --------------------------------------------------------------------- # Edit move6, receive less: 2 in negative stock # --------------------------------------------------------------------- - move6.with_context(svl=True).quantity_done = 8 + move6.quantity_done = 8 # stock_account values for move6 self.assertEqual(move6.stock_valuation_layer_ids[-1].remaining_qty, -2) @@ -1195,8 +1195,8 @@ class TestStockValuation(SavepointCase): self.assertEqual(len(move3.account_move_ids), 1) # the created account move is due to the receipt # nothing should have changed in the accounting regarding the output - self.assertEqual(self.product1.qty_available, 0) - self.assertEqual(self.product1.stock_value, 0) + self.assertEqual(self.product1.quantity_svl, 0) + self.assertEqual(self.product1.value_svl, 0) self.assertEqual(sum(self._get_stock_input_move_lines().mapped('debit')), 0) self.assertEqual(sum(self._get_stock_input_move_lines().mapped('credit')), 120) self.assertEqual(sum(self._get_stock_valuation_move_lines().mapped('debit')), 120) @@ -1419,7 +1419,6 @@ class TestStockValuation(SavepointCase): 'product_id': self.product2.id, 'product_uom': self.uom_unit.id, 'product_uom_qty': 10.0, - 'state': 'done', # simulate default_get override 'move_line_ids': [(0, 0, { 'product_id': self.product2.id, 'location_id': self.supplier_location.id, @@ -1445,7 +1444,7 @@ class TestStockValuation(SavepointCase): # --------------------------------------------------------------------- # Edit the previous stock move, receive 11 # --------------------------------------------------------------------- - move2.with_context(svl=True).quantity_done = 11 + move2.quantity_done = 11 self.assertEqual(sum(move2.stock_valuation_layer_ids.mapped('value')), 220.0) # after correction, the move should be valued at 11@20 self.assertEqual(sum(move2.stock_valuation_layer_ids.mapped('remaining_qty')), 11.0) @@ -1628,7 +1627,7 @@ class TestStockValuation(SavepointCase): # Add a new move line to receive 10 more # --------------------------------------------------------------------- self.assertEqual(len(move1.move_line_ids), 1) - self.env['stock.move.line'].with_context(svl=True).create({ + self.env['stock.move.line'].create({ 'move_id': move1.id, 'product_id': move1.product_id.id, 'qty_done': 10, @@ -1786,7 +1785,7 @@ class TestStockValuation(SavepointCase): # Edit last move, send 14 instead # it should send 2@10 and 4@12 # --------------------------------------------------------------------- - move3.with_context(svl=True).quantity_done = 14 + move3.quantity_done = 14 self.assertEqual(move3.product_qty, 14) # old value: -80 -(8@10) # new value: -148 => -(10@10 + 4@12) @@ -1879,7 +1878,7 @@ class TestStockValuation(SavepointCase): # --------------------------------------------------------------------- # Actually, send 8 in the last move # --------------------------------------------------------------------- - move2.with_context(svl=True).quantity_done = 8 + move2.quantity_done = 8 self.assertEqual(sum(move2.stock_valuation_layer_ids.mapped('value')), -80.0) # the move actually sent 8@10 @@ -1892,7 +1891,7 @@ class TestStockValuation(SavepointCase): # --------------------------------------------------------------------- # Actually, send 10 in the last move # --------------------------------------------------------------------- - move2.with_context(svl=True).quantity_done = 10 + move2.quantity_done = 10 self.assertEqual(sum(move2.stock_valuation_layer_ids.mapped('value')), -100.0) # the move actually sent 10@10 self.assertEqual(sum(self.product1.stock_valuation_layer_ids.mapped('remaining_qty')), 0) @@ -2068,7 +2067,7 @@ class TestStockValuation(SavepointCase): self.assertEqual(self.product1.quantity_svl, -5) self.assertEqual(self.product1.value_svl, -62.5) - move2.with_context(svl=True).move_line_ids.qty_done = 20 + move2.move_line_ids.qty_done = 20 self.assertEqual(self.product1.quantity_svl, 5) self.assertEqual(self.product1.value_svl, 87.5) @@ -2129,7 +2128,7 @@ class TestStockValuation(SavepointCase): move4._action_assign() move4.move_line_ids.qty_done = 10.0 move4._action_done() - move2.with_context(svl=True).move_line_ids.qty_done = 0 + move2.move_line_ids.qty_done = 0 self.assertEqual(self.product1.value_svl, -187.5) def test_average_perpetual_4(self): @@ -2268,7 +2267,7 @@ class TestStockValuation(SavepointCase): self.assertAlmostEqual(self.product1.quantity_svl, 15) self.assertAlmostEqual(self.product1.value_svl, 250) - move1.move_line_ids.with_context(svl=True).qty_done = 15 + move1.move_line_ids.qty_done = 15 self.assertAlmostEqual(self.product1.standard_price, 14.0) self.assertAlmostEqual(len(move1.stock_valuation_layer_ids), 2) @@ -2365,7 +2364,7 @@ class TestStockValuation(SavepointCase): self.assertEqual(move2_valuation_aml.debit, 0) self.assertEqual(move2_valuation_aml.credit, 200) - move2.with_context(svl=True).quantity_done = 10.0 + move2.quantity_done = 10.0 valuation_aml = self._get_stock_valuation_move_lines() move2_valuation_aml = valuation_aml[-1] @@ -2373,7 +2372,7 @@ class TestStockValuation(SavepointCase): self.assertEqual(move2_valuation_aml.debit, 100) self.assertEqual(move2_valuation_aml.credit, 0) - move2.with_context(svl=True).quantity_done = 11.0 + move2.quantity_done = 11.0 valuation_aml = self._get_stock_valuation_move_lines() move2_valuation_aml = valuation_aml[-1] @@ -3139,7 +3138,7 @@ class TestStockValuation(SavepointCase): self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date8)).value_svl, 712.5) # edit the done quantity of move1, decrease it - move1.with_context(svl=True).quantity_done = 5 + move1.quantity_done = 5 # the change is only visible right now self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date2)).quantity_svl, 10) @@ -3151,7 +3150,7 @@ class TestStockValuation(SavepointCase): self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date2)).value_svl, 100) # edit move 4, send 15 instead of 20 - move4.with_context(svl=True).quantity_done = 15 + move4.quantity_done = 15 # -(20*5) + (5*7.5) self.assertEqual(sum(move4.stock_valuation_layer_ids.mapped('value')), -62.5) @@ -3278,7 +3277,7 @@ class TestStockValuation(SavepointCase): self.assertEqual(self.product1.value_svl, 1275) # Edit the quantity done of move1, increase it. - move1.with_context(svl=True).quantity_done = 20 + move1.quantity_done = 20 self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date1)).quantity_svl, 10) self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date1)).value_svl, 100) diff --git a/addons/stock_account/tests/test_stockvaluationlayer.py b/addons/stock_account/tests/test_stockvaluationlayer.py index bc34803c87b..bb5692e3e8c 100644 --- a/addons/stock_account/tests/test_stockvaluationlayer.py +++ b/addons/stock_account/tests/test_stockvaluationlayer.py @@ -170,7 +170,7 @@ class TestStockValuationStandard(TestStockValuationCommon): move1 = self._make_in_move(self.product1, 10) move2 = self._make_in_move(self.product1, 10) move3 = self._make_out_move(self.product1, 15) - self.env['stock.move.line'].with_context(svl=True).create({ + self.env['stock.move.line'].create({ 'move_id': move1.id, 'product_id': move1.product_id.id, 'qty_done': 5, @@ -221,7 +221,7 @@ class TestStockValuationStandard(TestStockValuationCommon): move1 = self._make_in_move(self.product1, 10) move2 = self._make_out_move(self.product1, 15) - self.env['stock.move.line'].with_context(svl=True).create({ + self.env['stock.move.line'].create({ 'move_id': move1.id, 'product_id': move1.product_id.id, 'qty_done': 10, @@ -249,7 +249,7 @@ class TestStockValuationStandard(TestStockValuationCommon): self.product1.product_tmpl_id.categ_id.property_valuation = 'manual_periodic' move1 = self._make_dropship_move(self.product1, 10) - move1.move_line_ids.with_context(svl=True).qty_done = 15 + move1.move_line_ids.qty_done = 15 valuation_layers = self.product1.stock_valuation_layer_ids self.assertEqual(len(valuation_layers), 4) @@ -304,7 +304,7 @@ class TestStockValuationAVCO(TestStockValuationCommon): move1 = self._make_in_move(self.product1, 10, unit_cost=10) move2 = self._make_in_move(self.product1, 10, unit_cost=20) move3 = self._make_out_move(self.product1, 15) - self.env['stock.move.line'].with_context(svl=True).create({ + self.env['stock.move.line'].create({ 'move_id': move1.id, 'product_id': move1.product_id.id, 'qty_done': 5, @@ -321,7 +321,7 @@ class TestStockValuationAVCO(TestStockValuationCommon): move1 = self._make_in_move(self.product1, 10, unit_cost=10, create_picking=True) move2 = self._make_in_move(self.product1, 10, unit_cost=20) move3 = self._make_out_move(self.product1, 15) - self.env['stock.move.line'].with_context(svl=True).create({ + self.env['stock.move.line'].create({ 'product_id': move1.product_id.id, 'qty_done': 5, 'product_uom_id': move1.product_uom.id, @@ -478,7 +478,7 @@ class TestStockValuationFIFO(TestStockValuationCommon): move1 = self._make_in_move(self.product1, 20, unit_cost=10) move2 = self._make_out_move(self.product1, 10) move3 = self._make_out_move(self.product1, 10) - move1.move_line_ids.with_context(svl=True).qty_done = 10 + move1.move_line_ids.qty_done = 10 move4 = self._make_in_move(self.product1, 20, unit_cost=15) self.assertEqual(self.product1.value_svl, 150) @@ -501,7 +501,7 @@ class TestStockValuationFIFO(TestStockValuationCommon): move3 = self._make_out_move(self.product1, 15) move4 = self._make_out_move(self.product1, 20) move5 = self._make_in_move(self.product1, 100, unit_cost=15) - move1.move_line_ids.with_context(svl=True).qty_done = 20 + move1.move_line_ids.qty_done = 20 self.assertEqual(self.product1.value_svl, 1375) self.assertEqual(self.product1.quantity_svl, 95) diff --git a/addons/stock_account/views/product_views.xml b/addons/stock_account/views/product_views.xml index 3a36f18cdbe..e2815f500a2 100644 --- a/addons/stock_account/views/product_views.xml +++ b/addons/stock_account/views/product_views.xml @@ -80,23 +80,5 @@ - - Inventory Valuation - ir.actions.act_window - product.product - tree,form - form - - [('type', '=', 'product'), ('qty_available', '!=', 0)] - {'company_owned': True, 'create': False, 'edit' : False} - - -

- Create a new product valuation -

- If there are products, you will see its name and valuation. -

-
-
diff --git a/addons/stock_account/views/stock_account_views.xml b/addons/stock_account/views/stock_account_views.xml index 17cf9a232c2..f8af16cccc2 100644 --- a/addons/stock_account/views/stock_account_views.xml +++ b/addons/stock_account/views/stock_account_views.xml @@ -74,22 +74,6 @@ - - product.stock.tree.2 - product.product - - - - - - - - - -