summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Lejeune <sle@openerp.com>2019-05-22 09:40:17 +0000
committerSimon Lejeune <sle@openerp.com>2019-06-10 20:43:00 +0000
commit68d8e49f2fd03bdd42c9b50fde64f706af9d3c8b (patch)
tree27a226f9205eaf09ab78db821c3688d0fef162bd
parent1e5624ba680fa98dc17d3edd888b70405334ddca (diff)
[REF] stock_landed_costs: adapt to valuation layers
We need to increment the value of another layer. We don't want to write on this layer in place as we used to do for multiple reasons: - valuation at date: before the application of an lc, the value should not change - if the layer was consumed, we would need to adapt the consumers and if one day we would have needed to understand what happened in the fifo stack it would be hell So we allow to create a valuation layer linked to another valuation layer through a simple M2O O2M. At the time it is only used on the landed costs to add value and no quantity so the code expects it would be the case and it is not enforced. We adapt `_run_fifo` to use the full value. We also add a stat button on the landed cost to access the valuation layers, like what is done for the picking and manufacturing orders. task-1875873
-rw-r--r--addons/stock_account/models/product.py6
-rw-r--r--addons/stock_account/models/stock_valuation_layer.py2
-rw-r--r--addons/stock_account/views/stock_valuation_layer_views.xml1
-rw-r--r--addons/stock_landed_costs/__manifest__.py1
-rw-r--r--addons/stock_landed_costs/models/__init__.py2
-rw-r--r--addons/stock_landed_costs/models/stock_landed_cost.py41
-rw-r--r--addons/stock_landed_costs/models/stock_valuation_layer.py13
-rw-r--r--addons/stock_landed_costs/tests/__init__.py1
-rw-r--r--addons/stock_landed_costs/tests/test_stockvaluationlayer.py84
-rw-r--r--addons/stock_landed_costs/views/stock_landed_cost_views.xml6
-rw-r--r--addons/stock_landed_costs/views/stock_valuation_layer_views.xml13
11 files changed, 153 insertions, 17 deletions
diff --git a/addons/stock_account/models/product.py b/addons/stock_account/models/product.py
index 36f47692176..7d31503d62d 100644
--- a/addons/stock_account/models/product.py
+++ b/addons/stock_account/models/product.py
@@ -204,7 +204,11 @@ class ProductProduct(models.Model):
new_standard_price = candidate.unit_cost
qty_taken_on_candidate = min(qty_to_take_on_candidates, candidate.remaining_qty)
- value_taken_on_candidate = qty_taken_on_candidate * candidate.unit_cost
+ candidate_unit_cost = candidate.unit_cost
+ if candidate.stock_valuation_layer_ids:
+ candidate_unit_cost += sum(candidate.stock_valuation_layer_ids.mapped('value')) / candidate.remaining_qty
+
+ value_taken_on_candidate = qty_taken_on_candidate * candidate_unit_cost
candidate_vals = {
'remaining_qty': candidate.remaining_qty - qty_taken_on_candidate,
}
diff --git a/addons/stock_account/models/stock_valuation_layer.py b/addons/stock_account/models/stock_valuation_layer.py
index 4eb6f5c86a8..0382107380b 100644
--- a/addons/stock_account/models/stock_valuation_layer.py
+++ b/addons/stock_account/models/stock_valuation_layer.py
@@ -22,6 +22,8 @@ class StockValuationLayer(models.Model):
value = fields.Monetary('Total Value', readonly=True)
remaining_qty = fields.Float(digits=0, readonly=True)
description = fields.Char('Description', readonly=True)
+ stock_valuation_layer_id = fields.Many2one('stock.valuation.layer', 'Linked Stock Valuation Layer', readonly=True)
+ stock_valuation_layer_ids = fields.One2many('stock.valuation.layer', 'stock_valuation_layer_id')
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/views/stock_valuation_layer_views.xml b/addons/stock_account/views/stock_valuation_layer_views.xml
index bbcbc1af797..654877673db 100644
--- a/addons/stock_account/views/stock_valuation_layer_views.xml
+++ b/addons/stock_account/views/stock_valuation_layer_views.xml
@@ -28,6 +28,7 @@
<field name="description" />
<field name="account_move_id" attrs="{'invisible': [('account_move_id', '=', False)]}" />
<field name="company_id" groups="base.group_multi_company" />
+ <field name="stock_valuation_layer_id" attrs="{'invisible': [('stock_valuation_layer_id', '=', False)]}" />
</group>
</page>
</notebook>
diff --git a/addons/stock_landed_costs/__manifest__.py b/addons/stock_landed_costs/__manifest__.py
index 67e39d6810e..88e4de8dbd9 100644
--- a/addons/stock_landed_costs/__manifest__.py
+++ b/addons/stock_landed_costs/__manifest__.py
@@ -21,6 +21,7 @@ This module allows you to easily add extra costs on pickings and decide the spli
'data/stock_landed_cost_data.xml',
'views/product_views.xml',
'views/stock_landed_cost_views.xml',
+ 'views/stock_valuation_layer_views.xml',
'views/res_config_settings_views.xml',
],
'installable': True,
diff --git a/addons/stock_landed_costs/models/__init__.py b/addons/stock_landed_costs/models/__init__.py
index 15a94628e98..8b81c037995 100644
--- a/addons/stock_landed_costs/models/__init__.py
+++ b/addons/stock_landed_costs/models/__init__.py
@@ -3,3 +3,5 @@
from . import product
from . import stock_landed_cost
+from . import stock_valuation_layer
+
diff --git a/addons/stock_landed_costs/models/stock_landed_cost.py b/addons/stock_landed_costs/models/stock_landed_cost.py
index 1d513fdc746..fd524beaee0 100644
--- a/addons/stock_landed_costs/models/stock_landed_cost.py
+++ b/addons/stock_landed_costs/models/stock_landed_cost.py
@@ -9,12 +9,6 @@ from odoo.addons.stock_landed_costs.models import product
from odoo.exceptions import UserError
-class StockMove(models.Model):
- _inherit = 'stock.move'
-
- landed_cost_value = fields.Float('Landed Cost')
-
-
class LandedCost(models.Model):
_name = 'stock.landed.cost'
_description = 'Stock Landed Cost'
@@ -53,6 +47,7 @@ class LandedCost(models.Model):
required=True, states={'done': [('readonly', True)]})
company_id = fields.Many2one('res.company', string="Company",
related='account_journal_id.company_id', readonly=False)
+ stock_valuation_layer_ids = fields.One2many('stock.valuation.layer', 'stock_landed_cost_id')
@api.one
@api.depends('cost_lines.price_unit')
@@ -101,25 +96,33 @@ class LandedCost(models.Model):
'line_ids': [],
}
for line in cost.valuation_adjustment_lines.filtered(lambda line: line.move_id):
+ remaining_qty = sum(line.move_id.stock_valuation_layer_ids.mapped('remaining_qty'))
+ linked_layer = line.move_id.stock_valuation_layer_ids[-1] # Maybe the LC layer should be linked to multiple IN layer?
+
# Prorate the value at what's still in stock
- cost_to_add = (line.move_id.remaining_qty / line.move_id.product_qty) * line.additional_landed_cost
-
- new_landed_cost_value = line.move_id.landed_cost_value + line.additional_landed_cost
- line.move_id.write({
- 'landed_cost_value': new_landed_cost_value,
- 'value': line.move_id.value + line.additional_landed_cost,
- 'remaining_value': line.move_id.remaining_value + cost_to_add,
- 'price_unit': (line.move_id.value + line.additional_landed_cost) / line.move_id.product_qty,
+ cost_to_add = (remaining_qty / line.move_id.product_qty) * line.additional_landed_cost
+ valuation_layer = self.env['stock.valuation.layer'].create({
+ 'value': cost_to_add,
+ 'unit_cost': 0,
+ 'quantity': 0,
+ 'remaining_qty': 0,
+ 'stock_valuation_layer_id': linked_layer.id,
+ 'description': cost.name,
+ 'stock_move_id': line.move_id.id,
+ 'product_id': line.move_id.product_id.id,
+ 'stock_landed_cost_id': self.id,
+ 'company_id': self.company_id.id,
})
# `remaining_qty` is negative if the move is out and delivered proudcts that were not
# in stock.
qty_out = 0
if line.move_id._is_in():
- qty_out = line.move_id.product_qty - line.move_id.remaining_qty
+ qty_out = line.move_id.product_qty - remaining_qty
elif line.move_id._is_out():
qty_out = line.move_id.product_qty
move_vals['line_ids'] += line._create_accounting_entries(move, qty_out)
+ move_vals['stock_valuation_layer_ids'] = [(6, None, [valuation_layer.id])]
move = move.create(move_vals)
cost.write({'state': 'done', 'account_move_id': move.id})
move.post()
@@ -153,7 +156,7 @@ class LandedCost(models.Model):
'product_id': move.product_id.id,
'move_id': move.id,
'quantity': move.product_qty,
- 'former_cost': move.value,
+ 'former_cost': sum(move.stock_valuation_layer_ids.mapped('value')),
'weight': move.product_id.weight * move.product_qty,
'volume': move.product_id.volume * move.product_qty
}
@@ -227,6 +230,12 @@ class LandedCost(models.Model):
AdjustementLines.browse(key).write({'additional_landed_cost': value})
return True
+ def action_view_stock_valuation_layers(self):
+ self.ensure_one()
+ domain = [('id', 'in', self.stock_valuation_layer_ids.ids)]
+ action = self.env.ref('stock_account.stock_valuation_layer_action').read()[0]
+ return dict(action, domain=domain)
+
class LandedCostLine(models.Model):
_name = 'stock.landed.cost.lines'
diff --git a/addons/stock_landed_costs/models/stock_valuation_layer.py b/addons/stock_landed_costs/models/stock_valuation_layer.py
new file mode 100644
index 00000000000..56e2e82bcce
--- /dev/null
+++ b/addons/stock_landed_costs/models/stock_valuation_layer.py
@@ -0,0 +1,13 @@
+# -*- 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"""
+
+ _inherit = 'stock.valuation.layer'
+
+ stock_landed_cost_id = fields.Many2one('stock.landed.cost', 'Landed Cost')
+
diff --git a/addons/stock_landed_costs/tests/__init__.py b/addons/stock_landed_costs/tests/__init__.py
index 750868a9cbb..178d046e639 100644
--- a/addons/stock_landed_costs/tests/__init__.py
+++ b/addons/stock_landed_costs/tests/__init__.py
@@ -3,3 +3,4 @@
from . import test_stock_landed_costs
from . import test_stock_landed_costs_purchase
from . import test_stock_landed_costs_rounding
+from . import test_stockvaluationlayer
diff --git a/addons/stock_landed_costs/tests/test_stockvaluationlayer.py b/addons/stock_landed_costs/tests/test_stockvaluationlayer.py
new file mode 100644
index 00000000000..d09c4611080
--- /dev/null
+++ b/addons/stock_landed_costs/tests/test_stockvaluationlayer.py
@@ -0,0 +1,84 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+""" Implementation of "INVENTORY VALUATION TESTS (With valuation layers)" spreadsheet. """
+
+from odoo.tests import Form, tagged
+from odoo.addons.stock_account.tests.test_stockvaluationlayer import TestStockValuationCommon
+
+
+class TestStockValuationLC(TestStockValuationCommon):
+ @classmethod
+ def setUpClass(cls):
+ super(TestStockValuationLC, cls).setUpClass()
+ cls.productlc1 = cls.env['product.product'].create({
+ 'name': 'product1',
+ 'type': 'service',
+ 'landed_cost_ok': True,
+ 'categ_id': cls.env.ref('product.product_category_all').id,
+ })
+
+ def _make_lc(self, move, amount):
+ picking = move.picking_id
+ lc = Form(self.env['stock.landed.cost'])
+ lc.account_journal_id = self.env['account.journal'].search([('name', 'ilike', '%misc%')], limit=1)
+ lc.picking_ids.add(move.picking_id)
+ with lc.cost_lines.new() as cost_line:
+ cost_line.product_id = self.productlc1
+ cost_line.price_unit = amount
+ lc = lc.save()
+ lc.compute_landed_cost()
+ lc.button_validate()
+ return lc
+
+
+@tagged('post_install', '-at_install')
+class TestStockValuationLCFIFO(TestStockValuationLC):
+ def setUp(self):
+ super(TestStockValuationLCFIFO, self).setUp()
+ self.product1.product_tmpl_id.categ_id.property_cost_method = 'fifo'
+ self.product1.product_tmpl_id.categ_id.property_valuation = 'real_time'
+
+ def test_normal_1(self):
+ 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)
+ lc = self._make_lc(move1, 100)
+ move3 = self._make_out_move(self.product1, 1)
+
+ self.assertEqual(self.product1.value_svl, 380)
+ self.assertEqual(self.product1.quantity_svl, 19)
+
+ def test_negative_1(self):
+ self.product1.standard_price = 10
+ move1 = self._make_out_move(self.product1, 2, force_assign=True)
+ move2 = self._make_in_move(self.product1, 10, unit_cost=15, create_picking=True)
+ lc = self._make_lc(move2, 100)
+
+ self.assertEqual(self.product1.value_svl, 200)
+ self.assertEqual(self.product1.quantity_svl, 8)
+
+ def test_alreadyout_1(self):
+ move1 = self._make_in_move(self.product1, 10, unit_cost=10, create_picking=True)
+ move2 = self._make_out_move(self.product1, 10)
+ lc = self._make_lc(move1, 100)
+
+ self.assertEqual(self.product1.value_svl, 0)
+ self.assertEqual(self.product1.quantity_svl, 0)
+
+ def test_alreadyout_2(self):
+ 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)
+ move2 = self._make_out_move(self.product1, 1)
+ lc = self._make_lc(move1, 100)
+
+ self.assertEqual(self.product1.value_svl, 380)
+ self.assertEqual(self.product1.quantity_svl, 19)
+
+ def test_alreadyout_3(self):
+ move1 = self._make_in_move(self.product1, 10, unit_cost=10, create_picking=True)
+ move2 = self._make_out_move(self.product1, 10)
+ move1.move_line_ids.qty_done = 15
+ lc = self._make_lc(move1, 60)
+
+ self.assertEqual(self.product1.value_svl, 70)
+ self.assertEqual(self.product1.quantity_svl, 5)
diff --git a/addons/stock_landed_costs/views/stock_landed_cost_views.xml b/addons/stock_landed_costs/views/stock_landed_cost_views.xml
index cd5c9541228..a38175dd71c 100644
--- a/addons/stock_landed_costs/views/stock_landed_cost_views.xml
+++ b/addons/stock_landed_costs/views/stock_landed_cost_views.xml
@@ -13,6 +13,12 @@
<field name="state" widget="statusbar" statusbar_visible="draft,done"/>
</header>
<sheet>
+ <div class="oe_button_box" name="button_box">
+ <button string="Valuation" type="object"
+ name="action_view_stock_valuation_layers"
+ class="oe_stat_button" icon="fa-dollar" groups="stock.group_stock_manager"
+ attrs="{'invisible': [('state', 'not in', ['done'])]}" />
+ </div>
<div class="oe_title">
<label for="name" class="oe_edit_only"/>
<h1>
diff --git a/addons/stock_landed_costs/views/stock_valuation_layer_views.xml b/addons/stock_landed_costs/views/stock_valuation_layer_views.xml
new file mode 100644
index 00000000000..e7eea866c40
--- /dev/null
+++ b/addons/stock_landed_costs/views/stock_valuation_layer_views.xml
@@ -0,0 +1,13 @@
+<odoo>
+ <record id="stock_valuation_layer_form_inherited" model="ir.ui.view">
+ <field name="name">stock.valuation.layer.form.inherited</field>
+ <field name="model">stock.valuation.layer</field>
+ <field name="inherit_id" ref="stock_account.stock_valuation_layer_form" />
+ <field name="arch" type="xml">
+ <xpath expr="//field[@name='stock_move_id']" position="after">
+ <field name="stock_landed_cost_id" attrs="{'invisible': [('stock_landed_cost_id', '=', False)]}" />
+ </xpath>
+ </field>
+ </record>
+</odoo>
+