-
-
Notifications
You must be signed in to change notification settings - Fork 961
[18.0][IMP] purchase_stock_price_unit_sync: Sync the price corrected on the vendor bill #3139
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
carlosdauden
wants to merge
1
commit into
OCA:18.0
Choose a base branch
from
Tecnativa:18.0-IMP-purchase_stock_price_unit_sync-bill-price-diff
base: 18.0
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,3 @@ | ||
| # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). | ||
| from . import account_move_line | ||
| from . import purchase_order |
74 changes: 74 additions & 0 deletions
74
purchase_stock_price_unit_sync/models/account_move_line.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| # Copyright 2026 Tecnativa - Carlos Dauden | ||
| # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). | ||
|
|
||
| from odoo import models | ||
| from odoo.tools import float_compare | ||
|
|
||
|
|
||
| class AccountMoveLine(models.Model): | ||
| _inherit = "account.move.line" | ||
|
|
||
| def _is_price_unit_sync_restated(self, layer): | ||
| """Whether the price difference of this invoice line has to be applied | ||
| by restating the layer instead of by correcting only what is left of it. | ||
|
|
||
| It needs `product_cost_price_avco_sync`, which is what replays the chain | ||
| once the layer changes: restating it on its own, without that replay, | ||
| would leave the layer inconsistent and would also throw away the | ||
| correction Odoo does make, which is worse than not doing anything. | ||
|
|
||
| Refunds are left to Odoo, they have a compensation logic of their own, | ||
| and so is automated valuation, where the journal entry of the layer is | ||
| already posted and restating it would pull the two apart. | ||
| """ | ||
| self.ensure_one() | ||
| if not hasattr(self.env["stock.valuation.layer"], "_cost_price_avco_sync"): | ||
| return False | ||
| product = self.product_id.with_company(self.company_id) | ||
| return ( | ||
| not self.is_refund | ||
| and product.cost_method == "average" | ||
| and product.valuation != "real_time" | ||
| and layer.stock_move_id | ||
| and not layer.stock_valuation_layer_id | ||
| ) | ||
|
|
||
| def _prepare_pdiff_vals( | ||
| self, layer, aml, layer_price_unit, out_qty_to_invoice, qty_to_correct | ||
| ): | ||
| """Apply the invoiced price to the whole layer, not only to what is | ||
| still on hand. | ||
|
|
||
| Odoo corrects a price difference with a child layer worth the | ||
| difference times the quantity that has not left stock yet, and sends the | ||
| rest to the expense account. The stock ends up valued right, but the | ||
| moves that already left keep the cost that turned out to be wrong, so | ||
| the margin of what was sold, and any valuation asked for a date before | ||
| the invoice, stay wrong too. | ||
|
|
||
| Writing the invoiced price on the layer instead makes | ||
| `product_cost_price_avco_sync` replay the chain, which corrects both, | ||
| and leaves the bill on the same footing as changing the price on the | ||
| purchase order, which this module already syncs. Odoo has done the hard | ||
| part by the time this runs: which layer this invoice line pays for, and | ||
| at what price, comes from its own matching of layers and bills. | ||
| """ | ||
| if not self._is_price_unit_sync_restated(layer): | ||
| return super()._prepare_pdiff_vals( | ||
| layer, aml, layer_price_unit, out_qty_to_invoice, qty_to_correct | ||
| ) | ||
| # Same conversion Odoo does to compare the invoiced price with the layer | ||
| price_unit = aml._get_gross_unit_price() / aml.currency_rate | ||
| price_unit = aml.product_uom_id._compute_price( | ||
| price_unit, self.product_id.uom_id | ||
| ) | ||
| precision = max( | ||
| aml.currency_id.decimal_places, | ||
| layer.currency_id.decimal_places, | ||
| self.env["decimal.precision"].precision_get("Product Price"), | ||
| ) | ||
| if float_compare(price_unit, layer_price_unit, precision_digits=precision): | ||
| layer.unit_cost = price_unit | ||
| # Nothing is left for Odoo to create: the correction is already in the | ||
| # layer, and adding its child layer on top would count it twice. | ||
| return [], [] | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| The correction through the vendor bill needs `product_cost_price_avco_sync` | ||
| installed: it is that module which replays the valuation chain once the layer | ||
| changes, re-pricing what already left stock and correcting a valuation asked | ||
| for a date before the correction. Without it the bill keeps Odoo's standard | ||
| behaviour. | ||
|
|
||
| It applies to products with the **Average Cost (AVCO)** costing method and | ||
| **manual** inventory valuation. Refunds are left to Odoo, which compensates them | ||
| against the original bill with a logic of its own, and so is automated | ||
| valuation, where the journal entry of the layer is already posted and restating | ||
| it would pull stock valuation and accounting apart. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,12 @@ | ||
| This module allows to sync picking cost prices with purchase order line | ||
| price when moves are already done. | ||
|
|
||
| It does the same when the price is corrected on the **vendor bill**. Odoo | ||
| corrects a price difference with a child valuation layer worth the difference | ||
| times the quantity that has not left stock yet, and sends the rest to the | ||
| expense account, so the moves that already left keep the cost that turned out | ||
| to be wrong. Here the invoiced price is applied to the whole receipt instead, | ||
| which leaves the bill on the same footing as changing the price on the purchase | ||
| order. | ||
|
|
||
| Can be used with product_cost_price_avco_sync. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| There are two moments where the real price of a purchase shows up after the | ||
| goods have already been received, and both are synced: | ||
|
|
||
| - **The purchase order line.** Changing its price writes the new one on the | ||
| stock moves that are already done and on their valuation layers. | ||
| - **The vendor bill.** Posting it at a different price applies that price to the | ||
| whole receipt layer, instead of only to the part that has not left stock yet, | ||
| which is what Odoo does on its own. | ||
|
|
||
| Correcting the same price in both places does not count it twice: whichever runs | ||
| second finds the layer already worth what it says and does nothing. | ||
|
|
||
| With `product_cost_price_avco_sync` installed, either of them replays the | ||
| valuation chain, so the outgoing moves valued in between are re-priced and a | ||
| stock valuation asked for a date before the correction comes out right. Without | ||
| it, only the layers themselves are written and the vendor bill keeps Odoo's | ||
| standard behaviour. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,2 @@ | ||
| # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). | ||
|
|
||
| from . import test_bill_price_difference | ||
| from . import test_purchase_stock_price_unit_sync |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Based on your docstring, should this module have an explicit dependency on
product_cost_price_avco_syncin order to work correctly?