From 5c627006988edfb16df70ba04cdd53d6e34ab367 Mon Sep 17 00:00:00 2001 From: Simon Lejeune Date: Fri, 17 May 2019 14:33:41 +0000 Subject: [PATCH] [REF] stock_account: dropshipping valuation We standardize the dropshipping by handling it as it was an receipt directly followed by an out. We also follow the same logic whatever the cost method, ie we don't run fifo or update the AVCO. task-1875873 --- addons/stock_account/models/stock_move.py | 62 ++++++++++++++++++- .../stock_account/models/stock_move_line.py | 4 ++ .../tests/test_stockvaluation.py | 1 - .../tests/test_stockvaluationlayer.py | 54 ++++++++++++++++ 4 files changed, 118 insertions(+), 3 deletions(-) diff --git a/addons/stock_account/models/stock_move.py b/addons/stock_account/models/stock_move.py index a7a9884572f..89c69562a36 100644 --- a/addons/stock_account/models/stock_move.py +++ b/addons/stock_account/models/stock_move.py @@ -240,6 +240,52 @@ class StockMove(models.Model): svl_vals_list.append(svl_vals) return self.env['stock.valuation.layer'].create(svl_vals_list) + def _create_dropshipped_svl(self, forced_quantity=None): + """Create a `stock.valuation.layer` from `self`. + + :param forced_quantity: under some circunstances, the quantity to value is different than + the initial demand of the move (Default value = None) + """ + svl_vals_list = [] + for move in self: + valued_move_lines = move.move_line_ids + 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) + quantity = forced_quantity or valued_quantity + + unit_cost = move._get_price_unit() + if move.product_id.cost_method == 'standard': + unit_cost = move.product_id.standard_price + + common_vals = dict(move._prepare_common_svl_vals(), remaining_qty=0) + + # create the in + in_vals = { + 'unit_cost': unit_cost, + 'value': unit_cost * quantity, + 'quantity': quantity, + } + in_vals.update(common_vals) + svl_vals_list.append(in_vals) + + # create the out + out_vals = { + 'unit_cost': unit_cost, + 'value': unit_cost * quantity * -1, + 'quantity': quantity * -1, + } + out_vals.update(common_vals) + svl_vals_list.append(out_vals) + return self.env['stock.valuation.layer'].create(svl_vals_list) + + def _create_dropshipped_returned_svl(self, forced_quantity=None): + """Create a `stock.valuation.layer` from `self`. + + :param forced_quantity: under some circunstances, the quantity to value is different than + the initial demand of the move (Default value = None) + """ + return self._create_dropshipped_svl(forced_quantity=forced_quantity) @api.model def _run_fifo(self, move, quantity=None): @@ -734,9 +780,21 @@ class StockMove(models.Model): # Creates an account entry from stock_input to stock_output on a dropship move. https://github.com/odoo/odoo/issues/12687 journal_id, acc_src, acc_dest, acc_valuation = self._get_accounting_data_for_valuation() if self._is_dropshipped(): - self.with_context(force_company=self.company_id.id)._create_account_move_line(acc_src, acc_dest, journal_id) + value = self.env.context.get('force_valuation_amount') + if value > 0: + self.with_context(force_company=self.company_id.id)._create_account_move_line(acc_src, acc_valuation, journal_id) + else: + force_valuation_amount = self.env.context['force_valuation_amount'] + self.env.context = dict(self.env.context, force_valuation_amount=-1*force_valuation_amount) + self.with_context(force_company=self.company_id.id)._create_account_move_line(acc_valuation, acc_dest, journal_id) elif self._is_dropshipped_returned(): - self.with_context(force_company=self.company_id.id)._create_account_move_line(acc_dest, acc_src, journal_id) + value = self.env.context.get('force_valuation_amount') + if value > 0: + self.with_context(force_company=self.company_id.id)._create_account_move_line(acc_valuation, acc_src, journal_id) + else: + force_valuation_amount = self.env.context['force_valuation_amount'] + self.env.context = dict(self.env.context, force_valuation_amount=-1*force_valuation_amount) + self.with_context(force_company=self.company_id.id)._create_account_move_line(acc_dest, acc_valuation, journal_id) if self.company_id.anglo_saxon_accounting: #eventually reconcile together the invoice and valuation accounting entries on the stock interim accounts diff --git a/addons/stock_account/models/stock_move_line.py b/addons/stock_account/models/stock_move_line.py index aa2c9e53083..4cdee59c77b 100644 --- a/addons/stock_account/models/stock_move_line.py +++ b/addons/stock_account/models/stock_move_line.py @@ -110,6 +110,10 @@ class StockMoveLine(models.Model): stock_valuation_layers |= move._create_in_svl(forced_quantity=abs(diff)) elif move._is_in() and diff < 0 or move._is_out() and diff > 0: stock_valuation_layers |= move._create_out_svl(forced_quantity=abs(diff)) + elif move._is_dropshipped() and diff > 0 or move._is_dropshipped_returned() and diff < 0: + stock_valuation_layers |= move._create_dropshipped_svl(forced_quantity=abs(diff)) + elif move._is_dropshipped() and diff < 0 or move._is_dropshipped_returned() and diff > 0: + stock_valuation_layers |= move._create_dropshipped_returned_svl(forced_quantity=abs(diff)) for stock_valuation_layer in stock_valuation_layers: if not stock_valuation_layer.product_id.valuation == 'real_time': diff --git a/addons/stock_account/tests/test_stockvaluation.py b/addons/stock_account/tests/test_stockvaluation.py index aba66a6334b..66a695fc956 100644 --- a/addons/stock_account/tests/test_stockvaluation.py +++ b/addons/stock_account/tests/test_stockvaluation.py @@ -2276,7 +2276,6 @@ class TestStockValuation(SavepointCase): self.assertAlmostEqual(self.product1.quantity_svl, 25) self.assertAlmostEqual(self.product1.value_svl, 350) - @skip('need to implement dropship') def test_average_perpetual_8(self): """ Receive 1@10, then dropship 1@20, finally return the dropship. Dropship should not impact the price. diff --git a/addons/stock_account/tests/test_stockvaluationlayer.py b/addons/stock_account/tests/test_stockvaluationlayer.py index 1649c4a4c13..bc34803c87b 100644 --- a/addons/stock_account/tests/test_stockvaluationlayer.py +++ b/addons/stock_account/tests/test_stockvaluationlayer.py @@ -96,6 +96,24 @@ class TestStockValuationCommon(SavepointCase): self.days += 1 return out_move.with_context(svl=True) + def _make_dropship_move(self, product, quantity, unit_cost=None): + dropshipped = self.env['stock.move'].create({ + 'name': 'dropship %s units' % str(quantity), + 'product_id': product.id, + 'location_id': self.supplier_location.id, + 'location_dest_id': self.customer_location.id, + 'product_uom': self.uom_unit.id, + 'product_uom_qty': quantity, + 'picking_type_id': self.picking_type_out.id, + }) + if unit_cost: + dropshipped.unit_cost = unit_cost + dropshipped._action_confirm() + dropshipped._action_assign() + dropshipped.move_line_ids.qty_done = quantity + dropshipped._action_done() + return dropshipped + def _make_return(self, move, quantity_to_return): stock_return_picking = Form(self.env['stock.return.picking']\ .with_context(active_ids=[move.picking_id.id], active_id=move.picking_id.id, active_model='stock.picking')) @@ -215,6 +233,33 @@ class TestStockValuationStandard(TestStockValuationCommon): self.assertEqual(self.product1.value_svl, 50) self.assertEqual(self.product1.quantity_svl, 5) + def test_dropship_1(self): + self.product1.product_tmpl_id.categ_id.property_valuation = 'manual_periodic' + + move1 = self._make_dropship_move(self.product1, 10) + + valuation_layers = self.product1.stock_valuation_layer_ids + self.assertEqual(len(valuation_layers), 2) + self.assertEqual(valuation_layers[0].value, 100) + self.assertEqual(valuation_layers[1].value, -100) + self.assertEqual(self.product1.value_svl, 0) + self.assertEqual(self.product1.quantity_svl, 0) + + def test_change_in_past_increase_dropship_1(self): + 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 + + valuation_layers = self.product1.stock_valuation_layer_ids + self.assertEqual(len(valuation_layers), 4) + self.assertEqual(valuation_layers[0].value, 100) + self.assertEqual(valuation_layers[1].value, -100) + self.assertEqual(valuation_layers[2].value, 50) + self.assertEqual(valuation_layers[3].value, -50) + self.assertEqual(self.product1.value_svl, 0) + self.assertEqual(self.product1.quantity_svl, 0) + class TestStockValuationAVCO(TestStockValuationCommon): def setUp(self): @@ -381,6 +426,15 @@ class TestStockValuationAVCO(TestStockValuationCommon): self.assertEqual(self.product1.standard_price, 15) self.assertEqual(sum(self.product1.stock_valuation_layer_ids.mapped('remaining_qty')), 1) + def test_dropship_1(self): + move1 = self._make_in_move(self.product1, 1, unit_cost=10) + move2 = self._make_in_move(self.product1, 1, unit_cost=20) + move3 = self._make_dropship_move(self.product1, 1, unit_cost=10) + + self.assertEqual(self.product1.value_svl, 30) + self.assertEqual(self.product1.quantity_svl, 2) + self.assertEqual(self.product1.standard_price, 15) + class TestStockValuationFIFO(TestStockValuationCommon): def setUp(self):