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_account_avco_return_origin/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,
)
134 changes: 134 additions & 0 deletions stock_account_avco_return_origin/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
.. image:: https://odoo-community.org/readme-banner-image
:target: https://odoo-community.org/get-involved?utm_source=readme
:alt: Odoo Community Association

================================
Stock Account AVCO Return Origin
================================

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:f17913e624a9cac06a46f3e17cbe552389316d9a5f6dec7b4534bb7c37c54a5f
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fstock--logistics--workflow-lightgray.png?logo=github
:target: https://github.com/OCA/stock-logistics-workflow/tree/16.0/stock_account_avco_return_origin
:alt: OCA/stock-logistics-workflow
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/stock-logistics-workflow-16-0/stock-logistics-workflow-16-0-stock_account_avco_return_origin
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/stock-logistics-workflow&target_branch=16.0
:alt: Try me on Runboat

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

This module values supplier returns of average-cost (AVCO) products at
the **original receipt price** instead of the current average cost. The
outgoing valuation layer is corrected to the price of the move being
returned, and the product's average cost is realigned to the resulting
valuation, so subsequent issues use the correct cost.

The behavior is controlled by a company-level setting and applies to all
supplier returns of AVCO products once enabled. Customer returns and
non-AVCO products are unaffected.

**Table of contents**

.. contents::
:local:

Use Cases / Context
===================

When an average-cost (AVCO) product is returned to a supplier, standard
Odoo values the outgoing return move at the *current* average cost. If
the average cost has drifted since the original receipt (for example
because of a later receipt at a different price), the return removes the
wrong amount of value from inventory and the remaining average cost
becomes distorted.

Configuration
=============

To enable the feature:

1. Go to *Inventory > Configuration > Settings*.
2. Under *Valuation*, enable *Avco Return Origin Cost*.

Usage
=====

Once the setting is enabled, validating a supplier return of an AVCO
product values the return at the original receipt price and realigns the
product's average cost accordingly. The valuation layer's description
notes that the original receipt price was used.

If a return would empty the product's stock on hand (so that pricing it
at the original receipt price would leave residual value with no stock
to absorb it), that return is valued at the standard average cost
instead. This keeps the valuation consistent without blocking the
operation or requiring a manual revaluation.

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

Bugs are tracked on `GitHub Issues <https://github.com/OCA/stock-logistics-workflow/issues>`_.
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
`feedback <https://github.com/OCA/stock-logistics-workflow/issues/new?body=module:%20stock_account_avco_return_origin%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

Do not contact contributors directly about support or help with technical issues.

Credits
=======

Authors
-------

* Quartile

Contributors
------------

- `Quartile <https://www.quartile.co>`__:

- Aung Ko Ko Lin
- Toshikimi Shigenobu

Maintainers
-----------

This module is maintained by the OCA.

.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org

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.

.. |maintainer-nobuQuartile| image:: https://github.com/nobuQuartile.png?size=40px
:target: https://github.com/nobuQuartile
:alt: nobuQuartile
.. |maintainer-aungkokolin1997| image:: https://github.com/aungkokolin1997.png?size=40px
:target: https://github.com/aungkokolin1997
:alt: aungkokolin1997

Current `maintainers <https://odoo-community.org/page/maintainer-role>`__:

|maintainer-nobuQuartile| |maintainer-aungkokolin1997|

This module is part of the `OCA/stock-logistics-workflow <https://github.com/OCA/stock-logistics-workflow/tree/16.0/stock_account_avco_return_origin>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
1 change: 1 addition & 0 deletions stock_account_avco_return_origin/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
16 changes: 16 additions & 0 deletions stock_account_avco_return_origin/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Copyright 2026 Quartile (https://www.quartile.co)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

{
"name": "Stock Account AVCO Return Origin",
"summary": "Value AVCO supplier returns at the original receipt price.",
"version": "16.0.1.0.0",
"category": "Warehouse Management",
"author": "Quartile, Odoo Community Association (OCA)",
"website": "https://github.com/OCA/stock-logistics-workflow",
"license": "AGPL-3",
"depends": ["stock_account"],
"data": ["views/res_config_settings_views.xml"],
"installable": True,
"maintainers": ["nobuQuartile", "aungkokolin1997"],
}
71 changes: 71 additions & 0 deletions stock_account_avco_return_origin/i18n/ja.po
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * stock_account_avco_return_origin
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-23 01:40+0000\n"
"PO-Revision-Date: 2026-07-23 01:40+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"

#. module: stock_account_avco_return_origin
#: model:ir.model,name:stock_account_avco_return_origin.model_res_company
msgid "Companies"
msgstr "会社"

#. module: stock_account_avco_return_origin
#: model:ir.model,name:stock_account_avco_return_origin.model_res_config_settings
msgid "Config Settings"
msgstr "コンフィグ設定"

#. module: stock_account_avco_return_origin
#. odoo-python
#: code:addons/stock_account_avco_return_origin/models/product.py:0
#, python-format
msgid ""
"Original receipt price would result in negative inventory valuation; unit "
"cost capped at the remaining inventory value to keep the valuation non-"
"negative."
msgstr ""
"元の入荷価格では在庫評価額がマイナスになるため、単位原価を残存在庫評価額に制限"
"し、評価額がマイナスにならないようにしています。"

#. module: stock_account_avco_return_origin
#: model:ir.model,name:stock_account_avco_return_origin.model_product_product
msgid "Product Variant"
msgstr "プロダクトバリアント"

#. module: stock_account_avco_return_origin
#: model:ir.model,name:stock_account_avco_return_origin.model_stock_move
msgid "Stock Move"
msgstr "在庫移動"

#. module: stock_account_avco_return_origin
#: model:ir.model.fields,field_description:stock_account_avco_return_origin.field_res_company__avco_return_origin_cost
#: model:ir.model.fields,field_description:stock_account_avco_return_origin.field_res_config_settings__avco_return_origin_cost
msgid "Value AVCO Returns at Origin Cost"
msgstr "AVCO返品を元の原価で評価"

#. module: stock_account_avco_return_origin
#: model_terms:ir.ui.view,arch_db:stock_account_avco_return_origin.res_config_settings_view_form
msgid ""
"Value supplier returns of AVCO products at the\n"
" original receipt price instead of the current\n"
" average cost."
msgstr ""
"AVCO製品の仕入先返品を現在の平均原価ではなく\n"
" 元の入荷価格で評価します。"

#. module: stock_account_avco_return_origin
#. odoo-python
#: code:addons/stock_account_avco_return_origin/models/product.py:0
#, python-format
msgid "Valued at the original receipt price."
msgstr "元の入荷価格で評価されています。"
4 changes: 4 additions & 0 deletions stock_account_avco_return_origin/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from . import res_company
from . import res_config_settings
from . import product
from . import stock_move
49 changes: 49 additions & 0 deletions stock_account_avco_return_origin/models/product.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Copyright 2026 Quartile (https://www.quartile.co)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo import _, models
from odoo.tools import float_compare


class ProductProduct(models.Model):
_inherit = "product.product"

def _prepare_out_svl_vals(self, quantity, company):
vals = super()._prepare_out_svl_vals(quantity, company)
move_id = self.env.context.get("avco_origin_return_move_id")
if not move_id:
return vals
remaining_qty = self.quantity_svl + vals["quantity"]
if (
float_compare(remaining_qty, 0, precision_rounding=self.uom_id.rounding)
<= 0
):
return vals
move = self.env["stock.move"].browse(move_id)
unit_cost = move._get_price_unit()
value = company.currency_id.round(vals["quantity"] * unit_cost)
rounding_msg = _("Valued at the original receipt price.")
# A large price gap between receipts can make the origin-price return
# remove more value than remains on hand, pushing the valuation negative
# even though stock is still on hand. Cap the removal at the current
# inventory value so the valuation floors at zero instead of going
# negative, and realign unit_cost to the capped value.
if (
float_compare(
self.value_svl + value,
0,
precision_rounding=company.currency_id.rounding,
)
< 0
):
value = -self.value_svl
unit_cost = company.currency_id.round(value / vals["quantity"])
rounding_msg = _(
"Original receipt price would result in negative inventory"
" valuation; unit cost capped at the remaining inventory"
" value to keep the valuation non-negative."
)
vals["unit_cost"] = unit_cost
vals["value"] = value
vals["rounding_adjustment"] = "\n" + rounding_msg
return vals
10 changes: 10 additions & 0 deletions stock_account_avco_return_origin/models/res_company.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Copyright 2026 Quartile (https://www.quartile.co)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo import fields, models


class ResCompany(models.Model):
_inherit = "res.company"

avco_return_origin_cost = fields.Boolean("Value AVCO Returns at Origin Cost")
13 changes: 13 additions & 0 deletions stock_account_avco_return_origin/models/res_config_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright 2026 Quartile (https://www.quartile.co)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo import fields, models


class ResConfigSettings(models.TransientModel):
_inherit = "res.config.settings"

avco_return_origin_cost = fields.Boolean(
related="company_id.avco_return_origin_cost",
readonly=False,
)
62 changes: 62 additions & 0 deletions stock_account_avco_return_origin/models/stock_move.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Copyright 2026 Quartile (https://www.quartile.co)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo import models
from odoo.tools import float_compare


class StockMove(models.Model):
_inherit = "stock.move"

def _is_avco_origin_return(self):
self.ensure_one()
origin_move = self.origin_returned_move_id
return bool(
self.company_id.avco_return_origin_cost
and origin_move
and origin_move._is_in()
and origin_move.location_id.usage == "supplier"
and self.with_company(self.company_id).product_id.cost_method == "average"
)

def _sync_avco_origin_standard_price(self, product, company):
product = product.with_company(company)
product.invalidate_recordset(["value_svl", "quantity_svl"])
if (
float_compare(
product.quantity_svl, 0, precision_rounding=product.uom_id.rounding
)
<= 0
):
# Stock depleted (or negative): the emptying/over return was valued at
# standard AVCO (see product._prepare_out_svl_vals), so there is no
# residual to realign.
return
product.sudo().with_context(disable_auto_svl=True).standard_price = (
product.value_svl / product.quantity_svl
)

def _create_out_svl(self, forced_quantity=None):
origin_returns = self.filtered(lambda m: m._is_avco_origin_return())
normal = self - origin_returns
# When a single _action_done() batch mixes normal out moves with origin
# returns of the same product, the normal moves are intentionally valued
# first, at the average in effect before any return realignment, and the
# standard_price is realigned only once at the end (below). A batch has no
# defined move order, and core AVCO already values all batched out moves
# at the same pre-batch average; the return correction applies going
# forward, not retroactively to same-batch deliveries. Do not reorder.
layers = super(StockMove, normal)._create_out_svl(
forced_quantity=forced_quantity
)
for move in origin_returns:
layers |= super(
StockMove, move.with_context(avco_origin_return_move_id=move.id)
)._create_out_svl(forced_quantity=forced_quantity)
# Realign after each move so that a later return in the same batch
# sees an average reflecting the value already removed by earlier
# origin returns. Deferring this would let a return whose quantity
# empties stock fall back to a stale average and strand valuation
# (qty 0 with value != 0).
move._sync_avco_origin_standard_price(move.product_id, move.company_id)
return layers
4 changes: 4 additions & 0 deletions stock_account_avco_return_origin/readme/CONFIGURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
To enable the feature:

1. Go to *Inventory > Configuration > Settings*.
2. Under *Valuation*, enable *Avco Return Origin Cost*.
5 changes: 5 additions & 0 deletions stock_account_avco_return_origin/readme/CONTEXT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
When an average-cost (AVCO) product is returned to a supplier, standard Odoo
values the outgoing return move at the *current* average cost. If the average
cost has drifted since the original receipt (for example because of a later
receipt at a different price), the return removes the wrong amount of value
from inventory and the remaining average cost becomes distorted.
3 changes: 3 additions & 0 deletions stock_account_avco_return_origin/readme/CONTRIBUTORS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
- [Quartile](https://www.quartile.co):
- Aung Ko Ko Lin
- Toshikimi Shigenobu
Loading
Loading