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):