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
51 changes: 43 additions & 8 deletions purchase_discount/models/stock_move.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,38 @@
# Copyright 2018 Tecnativa - Pedro M. Baeza
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from contextlib import contextmanager, suppress

from odoo import models


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

@contextmanager
def _ensure_product_price_precision(self, po_line, discounted_price):
"""
Check if price_unit has enough decimals to store the precise discounted price.

If not, use more decimals and restore the old ones after yield.
"""
price_unit_precision = price_unit_precision_digits = False
if po_line.price_unit != discounted_price:
# We have to update the `decimal.precision` record
# because it is directly used in `super._get_price_unit`
price_unit_precision = (
self.env["decimal.precision"]
.sudo()
.search([("name", "=", "Product Price")])
)
price_unit_precision_digits = price_unit_precision.digits
price_unit_precision.digits += 8

yield

if price_unit_precision and price_unit_precision_digits:
price_unit_precision.digits = price_unit_precision_digits

def _get_price_unit(self):
"""Get correct price with discount replacing current price_unit
value before calling super and restoring it later for assuring
Expand All @@ -15,17 +41,26 @@ def _get_price_unit(self):
HACK: This is needed while https://github.com/odoo/odoo/pull/29983
is not merged.
"""
if hasattr(self.env, "ocb"):
return super()._get_price_unit()
price_unit = False
po_line = self.purchase_line_id.sudo()
price = po_line._get_discounted_price_unit()
if po_line and self.product_id == po_line.product_id:
price = po_line._get_discounted_price_unit()
if price != po_line.price_unit:
# Only change value if it's different
price_unit = po_line.price_unit
po_line.price_unit = price
res = super()._get_price_unit()
precision_context = self._ensure_product_price_precision(po_line, price)
else:
precision_context = suppress()

with precision_context:
if hasattr(self.env, "ocb"):
res = super()._get_price_unit()
else:
if po_line and self.product_id == po_line.product_id:
if price != po_line.price_unit:
# Only change value if it's different
price_unit = po_line.price_unit
po_line.price_unit = price

res = super()._get_price_unit()

if price_unit:
po_line.price_unit = price_unit
return res
44 changes: 44 additions & 0 deletions purchase_discount/tests/test_purchase_discount.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,50 @@ def test_move_price_unit(self):
self.assertAlmostEqual(self.po_line_1.price_unit, 10.0)
self.assertAlmostEqual(self.po_line_1.discount, 50.0)

def test_move_price_unit_rounding(self):
"""The stock valuation is correct when discount needs more precision."""
purchase_order = self.purchase_order
product = self.product_1
self.assertEqual(product.cost_method, "average")

# Create PO
purchase_order.order_line = False
with common.Form(purchase_order) as po_form:
with po_form.order_line.new() as line:
line.product_id = product
line.product_qty = 150
line.price_unit = 4.9
line.discount = 25
purchase_order.button_confirm()
picking = self.purchase_order.picking_ids

# Receive the picking
picking.action_assign()
transfer_wizard_action = picking.button_validate()
self.assertEqual(
transfer_wizard_action.get("res_model"), "stock.immediate.transfer"
)
transfer_wizard = common.Form(
self.env[transfer_wizard_action["res_model"]].with_context(
transfer_wizard_action["context"]
)
).save()
transfer_wizard.process()

# If the product price is rounded:
# 4.9 * (1 - 0.25) = 3.675 ~= 3.68
# => 3.68 * 150 = 552
# But the correct value is: 150 * 4.9 * (1 - 0.25) = 551.25
self.assertRecordValues(
product,
[
{
"value_svl": 551.25,
"standard_price": 3.68,
}
],
)

def test_report_price_unit(self):
rec = self.env["purchase.report"].search(
[("product_id", "=", self.product_1.id)]
Expand Down
142 changes: 142 additions & 0 deletions purchase_stock_cost_update/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
.. image:: https://odoo-community.org/readme-banner-image
:target: https://odoo-community.org/get-involved?utm_source=readme
:alt: Odoo Community Association

==========================
Update costs from purchase
==========================

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

.. |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-LGPL--3-blue.png
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fpurchase--workflow-lightgray.png?logo=github
:target: https://github.com/OCA/purchase-workflow/tree/14.0/purchase_stock_cost_update
:alt: OCA/purchase-workflow
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/purchase-workflow-14-0/purchase-workflow-14-0-purchase_stock_cost_update
: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/purchase-workflow&target_branch=14.0
:alt: Try me on Runboat

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

This module allows to adjust the valuation of the incoming goods related
to their purchase order from the purchase line itself.

**Table of contents**

.. contents::
:local:

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

When a purchase order is confirmed, the value of the received goods is
updated upon reception. It can happen anyway that the reception of the
goods is confirmed before the final price is recorded in the
corresponding purchase line.

We want to fix those disalignments as soon as possible from the purchase
order.

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

In order to use this module, you need to have some storable products
valued on average cost (AVCO).

To do so:

- Go to *Inventory > Configuration > Product Categories* and select one.
- In the **Inventory valuation** section, select the **Costing method**
as **Average Cost (AVCO)**.
- Now all the products in that category will have that valuation rules.

Usage
=====

In order to test the module:

- Go to *Purchase > Orders* and create a new quotation.
- Add a product with AVCO valuation and set a price.
- Validate the purchase order.
- Receive the products.
- The products are now valued at the price you set in the order line.
- You can check it in *Inventory > Reporting > Valuation* (debug mode
needed).
- Now change the price in the order line.
- You'll see that the line has changed its color to yellow and a new
button *Fix valuation* shows up in the header.
- When you click that button, every disaligned valuation will be fixed.
If you go to the *Valuation* report you'll see the adjustment layer.

Known issues / Roadmap
======================

- Only AVCO is supported

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

Bugs are tracked on `GitHub Issues <https://github.com/OCA/purchase-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/purchase-workflow/issues/new?body=module:%20purchase_stock_cost_update%0Aversion:%2014.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
-------

* Moduon

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

- David Vidal (`Moduon <https://www.moduon.team/>`__)
- `PyTech <https://www.pytech.it>`__:

- Simone Rubino <simone.rubino@pytech.it>

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-chienandalu| image:: https://github.com/chienandalu.png?size=40px
:target: https://github.com/chienandalu
:alt: chienandalu
.. |maintainer-rafaelbn| image:: https://github.com/rafaelbn.png?size=40px
:target: https://github.com/rafaelbn
:alt: rafaelbn

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

|maintainer-chienandalu| |maintainer-rafaelbn|

This module is part of the `OCA/purchase-workflow <https://github.com/OCA/purchase-workflow/tree/14.0/purchase_stock_cost_update>`_ 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 purchase_stock_cost_update/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
17 changes: 17 additions & 0 deletions purchase_stock_cost_update/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Copyright 2025 Moduon Team S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0.html).
{
"name": "Update costs from purchase",
"summary": "Allows to update valuation layers once the purchase is received",
"version": "14.0.1.0.0",
"category": "Purchase Management",
"author": "Moduon, Odoo Community Association (OCA)",
"maintainers": ["chienandalu", "rafaelbn"],
"website": "https://github.com/OCA/purchase-workflow",
"license": "LGPL-3",
"depends": ["purchase_stock"],
"data": [
"views/purchase_order_form_views.xml",
"views/stock_valuation_layer_views.xml",
],
}
99 changes: 99 additions & 0 deletions purchase_stock_cost_update/i18n/es.po
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * purchase_stock_cost_update
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-28 14:47+0000\n"
"PO-Revision-Date: 2025-10-28 15:50+0100\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 3.5\n"

#. module: purchase_stock_cost_update
#: model_terms:ir.ui.view,arch_db:purchase_stock_cost_update.purchase_order_form
msgid "<i class=\"fa fa-info-circle\" role=\"img\"/> Details"
msgstr "<i class=\"fa fa-info-circle\" role=\"img\"/> Detalles"

#. module: purchase_stock_cost_update
#: model_terms:ir.ui.view,arch_db:purchase_stock_cost_update.purchase_order_form
msgid ""
"<i class=\"fa fa-warning\"/> There are lines (marked in yellow) which\n"
" prices have changed since they where received in stock."
msgstr ""
"<i class=\"fa fa-warning\"/> Hay líneas (resaltadas en amarillo) cuyos\n"
" precios han cambiado desde que se recibieron en almacén."

#. module: purchase_stock_cost_update
#: model:ir.model.fields,field_description:purchase_stock_cost_update.field_stock_valuation_layer__cost_update_history
msgid "Cost Update History"
msgstr "Historial de actualizaciones de coste"

#. module: purchase_stock_cost_update
#: model_terms:ir.ui.view,arch_db:purchase_stock_cost_update.purchase_order_form
msgid "Fix valuation"
msgstr "Corregir valoración"

#. module: purchase_stock_cost_update
#. odoo-python
#: code:addons/purchase_stock_cost_update/models/purchase_order.py:0
#, python-format
msgid "Price difference layer created from %(line)s"
msgstr "Capa de diferencia de valoración creada desde %(line)s"

#. module: purchase_stock_cost_update
#: model:ir.model.fields,field_description:purchase_stock_cost_update.field_stock_valuation_layer__purchase_line_id
msgid "Purchase Line"
msgstr "Línea de compra"

#. module: purchase_stock_cost_update
#: model:ir.model,name:purchase_stock_cost_update.model_purchase_order
msgid "Purchase Order"
msgstr "Pedido de compra"

#. module: purchase_stock_cost_update
#: model:ir.model,name:purchase_stock_cost_update.model_purchase_order_line
msgid "Purchase Order Line"
msgstr "Línea de pedido de compra"

#. module: purchase_stock_cost_update
#: model:ir.model,name:purchase_stock_cost_update.model_stock_valuation_layer
msgid "Stock Valuation Layer"
msgstr "Capa de valoración de stock"

#. module: purchase_stock_cost_update
#: model_terms:ir.ui.view,arch_db:purchase_stock_cost_update.purchase_order_form
msgid ""
"This will make unreversible changes to the valuation layers related to the "
"affected products"
msgstr ""
"Esto realizará cambios irreversibles en las capas de valoración "
"relacionadas con los productos afectados"

#. module: purchase_stock_cost_update
#: model_terms:ir.ui.view,arch_db:purchase_stock_cost_update.stock_valuation_layer_form
msgid "Update history"
msgstr "Historial de actualizaciones"

#. module: purchase_stock_cost_update
#: model:ir.model.fields,field_description:purchase_stock_cost_update.field_purchase_order_line__valuation_difference
msgid "Valuation Difference"
msgstr "Diferencia de valoración"

#. module: purchase_stock_cost_update
#: model:ir.model.fields,field_description:purchase_stock_cost_update.field_purchase_order__valuation_difference_report
msgid "Valuation Difference Report"
msgstr "Informe de diferencias de valoración"

#. module: purchase_stock_cost_update
#: model:ir.model.fields,field_description:purchase_stock_cost_update.field_purchase_order__valuation_differs
#: model:ir.model.fields,field_description:purchase_stock_cost_update.field_purchase_order_line__valuation_differs
msgid "Valuation Differs"
msgstr "La valoración difiere"
Loading
Loading