Skip to content

Commit 2cc88f1

Browse files
[IMP] stock_valuation_fifo_lot: Allow revaluation for specific lot_id
1 parent 9d7ddec commit 2cc88f1

File tree

12 files changed

+384
-2
lines changed

12 files changed

+384
-2
lines changed

stock_valuation_fifo_lot/README.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@ that still exists in terms of FIFO costing, but not in reality, due to the incon
119119
carried over from the past) in the 'Force FIFO Lot/Serial' field so that this lot/serial
120120
is used for FIFO costing instead.
121121

122+
To revalue a product with lot_ids displayed in the Stock Valuation Layer list view,
123+
use the standard revaluation wizard. Set a lot_id in the wizard to revalue a specific
124+
lot only. Only lots with a remaining quantity will appear in the dropdown list.
125+
122126
Known issues / Roadmap
123127
======================
124128

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
22

33
from . import models
4+
from . import wizard
45
from .hooks import post_init_hook

stock_valuation_fifo_lot/__manifest__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"views/stock_move_line_views.xml",
1717
"views/stock_package_level_views.xml",
1818
"views/stock_valuation_layer_views.xml",
19+
"wizard/stock_valuation_layer_revaluation_views.xml",
1920
],
2021
"installable": True,
2122
"post_init_hook": "post_init_hook",

stock_valuation_fifo_lot/models/product.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,20 @@ def sorting_key(candidate):
5353
all_candidates = self._sort_by_all_candidates(all_candidates, sort_by)
5454
return all_candidates
5555

56-
# Depends on https://github.com/odoo/odoo/pull/180245
5756
def _get_qty_taken_on_candidate(self, qty_to_take_on_candidates, candidate):
5857
fifo_lot = self.env.context.get("fifo_lot")
5958
if fifo_lot:
6059
candidate_ml = candidate._get_unconsumed_in_move_line(fifo_lot)
6160
qty_to_take_on_candidates = min(
6261
qty_to_take_on_candidates, candidate_ml.qty_remaining
6362
)
63+
lot_revaluation_value = candidate._get_lot_revaluation_value(
64+
fifo_lot, qty_to_take_on_candidates
65+
)
66+
self.env["stock.valuation.layer"]._adjust_lot_revaluation_remaining_value(
67+
fifo_lot, qty_to_take_on_candidates
68+
)
69+
candidate_ml.value_consumed += lot_revaluation_value
6470
candidate_ml.qty_consumed += qty_to_take_on_candidates
6571
candidate_ml.value_consumed += qty_to_take_on_candidates * (
6672
candidate.remaining_value / candidate.remaining_qty
@@ -86,13 +92,18 @@ def _run_fifo(self, quantity, company):
8692
ml.qty_done, self.uom_id
8793
)
8894
fifo_qty = min(remaining_qty, moved_qty)
95+
lot_revaluation_value = self.env[
96+
"stock.valuation.layer"
97+
]._get_lot_revaluation_value(fifo_lot, fifo_qty)
8998
self = self.with_context(fifo_lot=fifo_lot, fifo_qty=fifo_qty)
9099
ml_fifo_vals = super()._run_fifo(fifo_qty, company)
91100
for key, value in ml_fifo_vals.items():
92101
if key in ("remaining_qty", "value"):
93102
vals[key] += value
94103
continue
95-
vals[key] = value # unit_cost
104+
vals[key] = value
105+
vals["value"] -= lot_revaluation_value
106+
vals["unit_cost"] = vals["value"] / fifo_qty
96107
remaining_qty -= fifo_qty
97108
if float_is_zero(remaining_qty, precision_rounding=self.uom_id.rounding):
98109
break

stock_valuation_fifo_lot/models/stock_move_line.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,12 @@ def _compute_remaining_value(self):
7171
* rec.qty_remaining
7272
/ remaining_qty
7373
)
74+
if not rec.qty_remaining:
75+
continue
76+
lot_revaluation_value = self.env[
77+
"stock.valuation.layer"
78+
]._get_lot_revaluation_value(rec.lot_id, rec.qty_remaining)
79+
rec.value_remaining += lot_revaluation_value
7480

7581
def _create_correction_svl(self, move, diff):
7682
# Pass the move line as a context value in case qty_done is overridden in a done

stock_valuation_fifo_lot/models/stock_valuation_layer.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,42 @@ class StockValuationLayer(models.Model):
1111
comodel_name="stock.lot",
1212
string="Lots/Serials",
1313
)
14+
is_lot_revaluation = fields.Boolean(
15+
help="Technical field to indicate that this SVL was generated "
16+
"from a FIFO lot revaluation."
17+
)
1418

1519
def _get_unconsumed_in_move_line(self, lot):
1620
self.ensure_one()
1721
return self.stock_move_id.move_line_ids.filtered(
1822
lambda x: x.lot_id == lot and x.qty_remaining
1923
)
24+
25+
def _get_lot_revaluation_data(self, lot, qty):
26+
lot_revaluation_layers = self.env["stock.valuation.layer"].search(
27+
[("lot_ids", "in", lot.ids), ("is_lot_revaluation", "=", True)]
28+
)
29+
move_lines = self.env["stock.move.line"].search(
30+
[("lot_id", "=", lot.id), ("qty_remaining", ">", 0.0)]
31+
)
32+
return lot_revaluation_layers, move_lines
33+
34+
def _get_lot_revaluation_value(self, lot, qty):
35+
lot_revaluation_layers, move_lines = self._get_lot_revaluation_data(lot, qty)
36+
if not lot_revaluation_layers or not move_lines:
37+
return 0.0
38+
lot_remaining_qty = sum(move_lines.mapped("qty_remaining"))
39+
lot_revaluation_value = (
40+
sum(lot_revaluation_layers.mapped("remaining_value"))
41+
* qty
42+
/ lot_remaining_qty
43+
)
44+
return lot_revaluation_value
45+
46+
def _adjust_lot_revaluation_remaining_value(self, lot, qty):
47+
lot_revaluation_layers, move_lines = self._get_lot_revaluation_data(lot, qty)
48+
if not lot_revaluation_layers or not move_lines:
49+
return
50+
lot_remaining_qty = sum(move_lines.mapped("qty_remaining"))
51+
for layer in lot_revaluation_layers:
52+
layer.remaining_value -= layer.remaining_value * qty / lot_remaining_qty

stock_valuation_fifo_lot/readme/USAGE.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,7 @@ been strictly FIFO). In such situations, you should select a "rogue" lot/serial
99
that still exists in terms of FIFO costing, but not in reality, due to the inconsistency
1010
carried over from the past) in the 'Force FIFO Lot/Serial' field so that this lot/serial
1111
is used for FIFO costing instead.
12+
13+
To revalue a product with lot_ids displayed in the Stock Valuation Layer list view,
14+
use the standard revaluation wizard. Set a lot_id in the wizard to revalue a specific
15+
lot only. Only lots with a remaining quantity will appear in the dropdown list.

stock_valuation_fifo_lot/static/description/index.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,9 @@ <h2><a class="toc-backref" href="#toc-entry-2">Usage</a></h2>
472472
that still exists in terms of FIFO costing, but not in reality, due to the inconsistency
473473
carried over from the past) in the ‘Force FIFO Lot/Serial’ field so that this lot/serial
474474
is used for FIFO costing instead.</p>
475+
<p>To revalue a product with lot_ids displayed in the Stock Valuation Layer list view,
476+
use the standard revaluation wizard. Set a lot_id in the wizard to revalue a specific
477+
lot only. Only lots with a remaining quantity will appear in the dropdown list.</p>
475478
</div>
476479
<div class="section" id="known-issues-roadmap">
477480
<h2><a class="toc-backref" href="#toc-entry-3">Known issues / Roadmap</a></h2>

stock_valuation_fifo_lot/tests/test_stock_valuation_fifo_lot.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,3 +288,57 @@ def test_avco_product_receipt(self):
288288
move_in.stock_valuation_layer_ids.lot_ids,
289289
"Lot IDs should be empty for AVCO product.",
290290
)
291+
292+
def test_fifo_revaluation_lot(self):
293+
receipt_picking, move_in = self.create_picking(
294+
self.supplier_location,
295+
self.stock_location,
296+
self.picking_type_in,
297+
["001", "002", "003"],
298+
100.0,
299+
)
300+
self.assertEqual(len(receipt_picking.move_line_ids), 3)
301+
svls = move_in.stock_valuation_layer_ids
302+
self.assertEqual(svls.remaining_value, 1500.0)
303+
self.assertEqual(svls.remaining_qty, 15.0)
304+
lot_001 = self.env["stock.lot"].search(
305+
[("product_id", "=", self.product.id), ("name", "=", "001")], limit=1
306+
)
307+
self.assertTrue(lot_001, "Lot 001 should exist")
308+
stock_valuation_account = self.env["account.account"].create(
309+
{
310+
"name": "Stock Valuation",
311+
"code": "StockValuation",
312+
"account_type": "asset_current",
313+
"reconcile": True,
314+
}
315+
)
316+
revaluation = self.env["stock.valuation.layer.revaluation"].create(
317+
{
318+
"product_id": self.product.id,
319+
"company_id": self.env.company.id,
320+
"added_value": 10.0,
321+
"lot_id": lot_001.id,
322+
"reason": "Test Revaluation Lot 001",
323+
"account_id": stock_valuation_account.id,
324+
}
325+
)
326+
revaluation.action_validate_revaluation()
327+
svl_revaluation = self.env["stock.valuation.layer"].search(
328+
[
329+
("product_id", "=", self.product.id),
330+
("lot_ids", "in", lot_001.ids),
331+
],
332+
limit=1,
333+
order="id desc",
334+
)
335+
self.assertTrue(svl_revaluation, "Revaluation SVL should be created")
336+
self.assertIn(
337+
lot_001, svl_revaluation.lot_ids, "Revaluation SVL should have correct lot"
338+
)
339+
self.assertEqual(svl_revaluation.value, 10.0)
340+
self.assertEqual(
341+
svl_revaluation.remaining_value,
342+
10.0,
343+
"Remaining value should be added in revaluation SVL",
344+
)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from . import stock_valuation_layer_revaluation

0 commit comments

Comments
 (0)