Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions setup/stock_valuation_fifo_lot_mrp_landed_cost/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import setuptools

setuptools.setup(
setup_requires=['setuptools-odoo'],
odoo_addon=True,
)
28 changes: 27 additions & 1 deletion stock_valuation_fifo_lot/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,28 @@ Stock Valuation Fifo Lot

|badge1| |badge2| |badge3| |badge4| |badge5|

This module is used to calculate FIFO cost by lot.
This module is used to apply FIFO cost calculation at the lot or serial level instead of the product level.

Example: Lot-Level Costing
~~~~~~~~~~~~~~~~~~~~~~~~~~

- Purchase:

- Lot A: 100 units at $10 each.
- Lot B: 100 units at $12 each.

- Sale:

- 50 units from Lot B.

- COGS Calculation:

- 50 units * $12 = $600 assigned to COGS.

- Ending Inventory:

- Lot A: 100 units at $10 each.
- Lot B: 50 units at $12 each.

.. IMPORTANT::
This is an alpha version, the data model and design can change at any time without warning.
Expand All @@ -40,6 +61,11 @@ This module is used to calculate FIFO cost by lot.
.. contents::
:local:

Usage
=====

Process an outgoing move with a lot/serial for a product of FIFO costing method, the costs will be calculated based on the lot/serial.

Bug Tracker
===========

Expand Down
1 change: 0 additions & 1 deletion stock_valuation_fifo_lot/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,3 @@
from . import product
from . import stock_move
from . import stock_valuation_layer
from . import stock_landed_cost
9 changes: 8 additions & 1 deletion stock_valuation_fifo_lot/models/stock_move.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ def _get_price_unit(self):
not self.purchase_line_id
and self.product_id.cost_method == "fifo"
and len(self.lot_ids) == 1
and not (
self.origin_returned_move_id
and self.origin_returned_move_id.sudo().stock_valuation_layer_ids
)
):
candidates = (
self.env["stock.valuation.layer"]
Expand All @@ -93,5 +97,8 @@ def _get_price_unit(self):
)
)
if candidates:
price_unit = candidates[0].unit_cost
candidates = candidates[0] | candidates[0].stock_valuation_layer_ids
price_unit = sum(candidates.mapped("value")) / sum(
candidates.mapped("quantity")
)
return price_unit
23 changes: 22 additions & 1 deletion stock_valuation_fifo_lot/readme/DESCRIPTION.rst
Original file line number Diff line number Diff line change
@@ -1 +1,22 @@
This module is used to calculate FIFO cost by lot.
This module is used to apply FIFO cost calculation at the lot or serial level instead of the product level.

Example: Lot-Level Costing
~~~~~~~~~~~~~~~~~~~~~~~~~~

- Purchase:

- Lot A: 100 units at $10 each.
- Lot B: 100 units at $12 each.

- Sale:

- 50 units from Lot B.

- COGS Calculation:

- 50 units * $12 = $600 assigned to COGS.

- Ending Inventory:

- Lot A: 100 units at $10 each.
- Lot B: 50 units at $12 each.
1 change: 1 addition & 0 deletions stock_valuation_fifo_lot/readme/USAGE.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Process an outgoing move with a lot/serial for a product of FIFO costing method, the costs will be calculated based on the lot/serial.
62 changes: 44 additions & 18 deletions stock_valuation_fifo_lot/static/description/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@

/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $
:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
:Copyright: This stylesheet has been placed in the public domain.

Default cascading style sheet for the HTML output of Docutils.
Despite the name, some widely supported CSS2 features are used.

See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
customize this style sheet.
Expand Down Expand Up @@ -274,7 +275,7 @@
margin-left: 2em ;
margin-right: 2em }

pre.code .ln { color: grey; } /* line numbers */
pre.code .ln { color: gray; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
Expand All @@ -300,7 +301,7 @@
span.pre {
white-space: pre }

span.problematic {
span.problematic, pre.problematic {
color: red }

span.section-subtitle {
Expand Down Expand Up @@ -369,7 +370,29 @@ <h1 class="title">Stock Valuation Fifo Lot</h1>
!! source digest: sha256:877af52a350ab6a61b6c128c4fbcffe909e4a8274c8ec064390dc9b1c36d9253
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Alpha" src="https://img.shields.io/badge/maturity-Alpha-red.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/stock-logistics-workflow/tree/15.0/stock_valuation_fifo_lot"><img alt="OCA/stock-logistics-workflow" src="https://img.shields.io/badge/github-OCA%2Fstock--logistics--workflow-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/stock-logistics-workflow-15-0/stock-logistics-workflow-15-0-stock_valuation_fifo_lot"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/stock-logistics-workflow&amp;target_branch=15.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>This module is used to calculate FIFO cost by lot.</p>
<p>This module is used to apply FIFO cost calculation at the lot or serial level instead of the product level.</p>
<div class="section" id="example-lot-level-costing">
<h1>Example: Lot-Level Costing</h1>
<ul class="simple">
<li>Purchase:<ul>
<li>Lot A: 100 units at $10 each.</li>
<li>Lot B: 100 units at $12 each.</li>
</ul>
</li>
<li>Sale:<ul>
<li>50 units from Lot B.</li>
</ul>
</li>
<li>COGS Calculation:<ul>
<li>50 units * $12 = $600 assigned to COGS.</li>
</ul>
</li>
<li>Ending Inventory:<ul>
<li>Lot A: 100 units at $10 each.</li>
<li>Lot B: 50 units at $12 each.</li>
</ul>
</li>
</ul>
<div class="admonition important">
<p class="first admonition-title">Important</p>
<p class="last">This is an alpha version, the data model and design can change at any time without warning.
Expand All @@ -379,33 +402,35 @@ <h1 class="title">Stock Valuation Fifo Lot</h1>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-1">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-2">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-3">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="toc-entry-4">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-5">Maintainers</a></li>
</ul>
</li>
<li><a class="reference internal" href="#usage" id="toc-entry-1">Usage</a></li>
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-2">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-3">Credits</a></li>
</ul>
</div>
<div class="section" id="usage">
<h2><a class="toc-backref" href="#toc-entry-1">Usage</a></h2>
<p>Process an outgoing move with a lot/serial for a product of FIFO costing method, the costs will be calculated based on the lot/serial.</p>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#toc-entry-1">Bug Tracker</a></h1>
<h2><a class="toc-backref" href="#toc-entry-2">Bug Tracker</a></h2>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/stock-logistics-workflow/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/stock-logistics-workflow/issues/new?body=module:%20stock_valuation_fifo_lot%0Aversion:%2015.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#toc-entry-2">Credits</a></h1>
<h2><a class="toc-backref" href="#toc-entry-3">Credits</a></h2>
</div>
</div>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#toc-entry-3">Authors</a></h2>
<h1>Authors</h1>
<ul class="simple">
<li>Ecosoft</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#toc-entry-4">Contributors</a></h2>
<h1>Contributors</h1>
<ul class="simple">
<li><a class="reference external" href="http://ecosoft.co.th">Ecosoft</a>:<ul>
<li>Tharathip Chaweewongphan &lt;<a class="reference external" href="mailto:tharathipc&#64;ecosoft.co.th">tharathipc&#64;ecosoft.co.th</a>&gt;</li>
Expand All @@ -416,9 +441,11 @@ <h2><a class="toc-backref" href="#toc-entry-4">Contributors</a></h2>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#toc-entry-5">Maintainers</a></h2>
<h1>Maintainers</h1>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
<a class="reference external image-reference" href="https://odoo-community.org">
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
</a>
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.</p>
Expand All @@ -428,6 +455,5 @@ <h2><a class="toc-backref" href="#toc-entry-5">Maintainers</a></h2>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</body>
</html>
4 changes: 4 additions & 0 deletions stock_valuation_fifo_lot/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from . import common
from . import test_stock_valuation_fifo_lot
137 changes: 137 additions & 0 deletions stock_valuation_fifo_lot/tests/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# Copyright 2024 Quartile (https://www.quartile.co)
# Copyright 2025 Alberto Martínez <alberto.martinez@sygel.es>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo.tests.common import Form, TransactionCase


class TestStockValuationFifoCommon(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.product_categ = cls.env["product.category"].create(
{
"name": "Test Category",
"property_cost_method": "fifo",
}
)
cls.product = cls.env["product.product"].create(
{
"name": "Test Product",
"type": "product",
"categ_id": cls.product_categ.id,
"tracking": "lot",
}
)
cls.vendor_loc = cls.env.ref("stock.stock_location_suppliers")
cls.cust_loc = cls.env.ref("stock.stock_location_customers")
cls.stock_loc = cls.env.ref("stock.stock_location_stock")
cls.pick_type_in = cls.env.ref("stock.picking_type_in")
cls.pick_type_out = cls.env.ref("stock.picking_type_out")
cls.landed_cost = cls.env["product.product"].create(
{
"name": "Landed Cost",
"detailed_type": "service",
"landed_cost_ok": True,
"property_account_expense_id": cls.env.ref(
"l10n_generic_coa.1_expense"
).id,
}
)
cls.lot1 = cls.env["stock.production.lot"].create(
{
"name": "0001",
"product_id": cls.product.id,
"company_id": cls.env.company.id,
}
)

def create_picking(self, op_type, lot_ids, ml_qty=5.0, price=0.0):
loc = self.vendor_loc
loc_dest = self.stock_loc
pick_type = self.pick_type_in
if op_type == "out":
loc = self.stock_loc
loc_dest = self.cust_loc
pick_type = self.pick_type_out
pick = self.env["stock.picking"].create(
{
"location_id": loc.id,
"location_dest_id": loc_dest.id,
"picking_type_id": pick_type.id,
}
)
move = self.env["stock.move"].create(
{
"name": "Test",
"product_id": self.product.id,
"location_id": loc.id,
"location_dest_id": loc_dest.id,
"product_uom": self.product.uom_id.id,
"product_uom_qty": ml_qty * len(lot_ids),
"picking_id": pick.id,
"price_unit": price,
}
)
for lot in lot_ids:
self.env["stock.move.line"].create(
{
"move_id": move.id,
"picking_id": pick.id,
"product_id": self.product.id,
"location_id": loc.id,
"location_dest_id": loc_dest.id,
"product_uom_id": move.product_uom.id,
"qty_done": ml_qty,
"lot_id": lot.id,
}
)
pick.action_confirm()
pick.action_assign()
pick._action_done()
return pick, move

def create_landed_cost(self, picking, cost):
landed_cost = self.env["stock.landed.cost"].create(
dict(
picking_ids=[(6, 0, [picking.id])],
account_journal_id=self.env.company.lc_journal_id.id
or self.env["ir.property"]
._get("property_stock_journal", "product.category")
.id,
cost_lines=[
(
0,
0,
{
"name": "equal split",
"split_method": "equal",
"price_unit": cost,
"product_id": self.landed_cost.id,
},
)
],
)
)
landed_cost.compute_landed_cost()
landed_cost.button_validate()
return landed_cost

def return_picking(self, picking, return_qty):
return_picking_wizard_form = Form(
self.env["stock.return.picking"].with_context(
active_ids=picking.ids,
active_id=picking.id,
active_model="stock.picking",
)
)
return_picking_wizard = return_picking_wizard_form.save()
return_picking_wizard.product_return_moves.write({"quantity": return_qty})
return_picking_wizard_action = return_picking_wizard.create_returns()
return_picking = self.env["stock.picking"].browse(
return_picking_wizard_action["res_id"]
)
return_move = return_picking.move_ids_without_package
return_move.move_line_ids.qty_done = return_qty
return_picking.button_validate()
return return_picking
22 changes: 22 additions & 0 deletions stock_valuation_fifo_lot/tests/test_stock_valuation_fifo_lot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Copyright 2025 Alberto Martínez <alberto.martinez@sygel.es>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo.addons.stock_valuation_fifo_lot.tests.common import (
TestStockValuationFifoCommon,
)


class TestStockValuationFifoLot(TestStockValuationFifoCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()

def test_out_picking_return(self):
in_pick, in_moves = self.create_picking("in", self.lot1, ml_qty=5, price=10)
self.create_landed_cost(in_pick, 10)
out_pick, out_moves = self.create_picking("out", self.lot1, 1)
self.return_picking(out_pick, 1)
svls = self.env["stock.valuation.layer"].search(
[("product_id", "=", self.product.id)]
)
self.assertEqual(60, sum(svls.mapped("value")))
Loading