Skip to content

Commit

Permalink
Merge PR #1723 into 15.0
Browse files Browse the repository at this point in the history
Signed-off-by LoisRForgeFlow
  • Loading branch information
OCA-git-bot committed Nov 15, 2024
2 parents ba1da80 + 700e893 commit 869af96
Showing 1 changed file with 156 additions and 128 deletions.
284 changes: 156 additions & 128 deletions stock_account_product_run_fifo_hook/hooks.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Copyright 2020 ForgeFlow, S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from collections import defaultdict
from datetime import datetime

from odoo import _
from odoo.tools import float_is_zero

Expand Down Expand Up @@ -122,154 +125,179 @@ def _run_fifo_vacuum_new(self, company=None):
return self._run_fifo_vacuum_original(company=company)
if company is None:
company = self.env.company
svls_to_vacuum = (
self.env["stock.valuation.layer"]
.sudo()
.search(
[
("product_id", "in", self.ids),
("remaining_qty", "<", 0),
("stock_move_id", "!=", False),
("company_id", "=", company.id),
],
order="create_date, id",
)
ValuationLayer = self.env["stock.valuation.layer"].sudo()
svls_to_vacuum_by_product = defaultdict(lambda: ValuationLayer)
res = ValuationLayer.read_group(
[
("product_id", "in", self.ids),
("remaining_qty", "<", 0),
("stock_move_id", "!=", False),
("company_id", "=", company.id),
],
["ids:array_agg(id)", "create_date:min"],
["product_id"],
orderby="create_date, id",
)
if not svls_to_vacuum:
return

as_svls = []

min_create_date = datetime.max
for group in res:
svls_to_vacuum_by_product[group["product_id"][0]] = ValuationLayer.browse(
group["ids"]
)
min_create_date = min(min_create_date, group["create_date"])
all_candidates_by_product = defaultdict(lambda: ValuationLayer)
domain = [
("company_id", "=", company.id),
("product_id", "in", self.ids),
("remaining_qty", ">", 0),
("create_date", ">=", svls_to_vacuum[0].create_date),
("company_id", "=", company.id),
("create_date", ">=", min_create_date),
]
if self.env.context.get("use_past_svl", False):
domain = [
("company_id", "=", company.id),
("product_id", "in", self.ids),
("remaining_qty", ">", 0),
]
all_candidates = self.env["stock.valuation.layer"].sudo().search(domain)
for svl_to_vacuum in svls_to_vacuum:
# We don't use search to avoid executing _flush_search and to decrease interaction with DB
candidates = all_candidates.filtered(
lambda r: r.create_date > svl_to_vacuum.create_date
or r.create_date == svl_to_vacuum.create_date
and r.id > svl_to_vacuum.id
domain = domain[:3]
res = ValuationLayer.sudo().read_group(
domain,
["ids:array_agg(id)"],
["product_id"],
orderby="id",
)
for group in res:
all_candidates_by_product[group["product_id"][0]] = ValuationLayer.browse(
group["ids"]
)
if self.env.context.get("use_past_svl", False):
candidates = all_candidates
if not candidates:
break
qty_to_take_on_candidates = abs(svl_to_vacuum.remaining_qty)
qty_taken_on_candidates = 0
tmp_value = 0
taken_data = {}
for candidate in candidates:
qty_taken_on_candidate = min(
candidate.remaining_qty, qty_to_take_on_candidates

new_svl_vals_real_time = []
new_svl_vals_manual = []
real_time_svls_to_vacuum = ValuationLayer

for product in self:
all_candidates = all_candidates_by_product[product.id]
current_real_time_svls = ValuationLayer
for svl_to_vacuum in svls_to_vacuum_by_product[product.id]:
# We don't use search to avoid executing _flush_search and to decrease interaction with DB
candidates = all_candidates.filtered(
lambda r: r.create_date > svl_to_vacuum.create_date
or r.create_date == svl_to_vacuum.create_date
and r.id > svl_to_vacuum.id
)
taken_data[candidate.id] = {"quantity": qty_taken_on_candidate}
qty_taken_on_candidates += qty_taken_on_candidate
if self.env.context.get("use_past_svl", False):
candidates = all_candidates
if not candidates:
break
qty_to_take_on_candidates = abs(svl_to_vacuum.remaining_qty)
qty_taken_on_candidates = 0
tmp_value = 0
taken_data = {}
for candidate in candidates:
qty_taken_on_candidate = min(
candidate.remaining_qty, qty_to_take_on_candidates
)
taken_data[candidate.id] = {"quantity": qty_taken_on_candidate}
qty_taken_on_candidates += qty_taken_on_candidate

candidate_unit_cost = (
candidate.remaining_value / candidate.remaining_qty
)
value_taken_on_candidate = (
qty_taken_on_candidate * candidate_unit_cost
)
value_taken_on_candidate = candidate.currency_id.round(
value_taken_on_candidate
)
taken_data[candidate.id].update(
{
"value": value_taken_on_candidate,
}
)
new_remaining_value = (
candidate.remaining_value - value_taken_on_candidate
)

candidate_unit_cost = (
candidate.remaining_value / candidate.remaining_qty
candidate_vals = {
"remaining_qty": candidate.remaining_qty
- qty_taken_on_candidate,
"remaining_value": new_remaining_value,
}
# Start Hook
candidate_vals = self._run_fifo_vacuum_prepare_candidate_update(
svl_to_vacuum,
candidate,
qty_taken_on_candidate,
value_taken_on_candidate,
candidate_vals,
)
# End Hook
candidate.write(candidate_vals)
if not (candidate.remaining_qty > 0):
all_candidates -= candidate

qty_to_take_on_candidates -= qty_taken_on_candidate
tmp_value += value_taken_on_candidate
if float_is_zero(
qty_to_take_on_candidates,
precision_rounding=product.uom_id.rounding,
):
break

# Get the estimated value we will correct.
remaining_value_before_vacuum = (
svl_to_vacuum.unit_cost * qty_taken_on_candidates
)
value_taken_on_candidate = qty_taken_on_candidate * candidate_unit_cost
value_taken_on_candidate = candidate.currency_id.round(
value_taken_on_candidate
new_remaining_qty = (
svl_to_vacuum.remaining_qty + qty_taken_on_candidates
)
taken_data[candidate.id].update(
corrected_value = remaining_value_before_vacuum - tmp_value
svl_to_vacuum.with_context(taken_data=taken_data).write(
{
"value": value_taken_on_candidate,
"remaining_qty": new_remaining_qty,
}
)
new_remaining_value = (
candidate.remaining_value - value_taken_on_candidate
)

candidate_vals = {
"remaining_qty": candidate.remaining_qty - qty_taken_on_candidate,
"remaining_value": new_remaining_value,
}
# Start Hook
candidate_vals = self._run_fifo_vacuum_prepare_candidate_update(
svl_to_vacuum,
candidate,
qty_taken_on_candidate,
value_taken_on_candidate,
candidate_vals,
)
# End Hook
candidate.write(candidate_vals)
if not (candidate.remaining_qty > 0):
all_candidates -= candidate

qty_to_take_on_candidates -= qty_taken_on_candidate
tmp_value += value_taken_on_candidate
if float_is_zero(
qty_to_take_on_candidates, precision_rounding=self.uom_id.rounding
):
break

# Get the estimated value we will correct.
remaining_value_before_vacuum = (
svl_to_vacuum.unit_cost * qty_taken_on_candidates
)
new_remaining_qty = svl_to_vacuum.remaining_qty + qty_taken_on_candidates
corrected_value = remaining_value_before_vacuum - tmp_value
svl_to_vacuum.with_context(taken_data=taken_data).write(
{
"remaining_qty": new_remaining_qty,
}
)
# Don't create a layer or an accounting entry if the
# corrected value is zero.
if svl_to_vacuum.currency_id.is_zero(corrected_value):
continue

# Don't create a layer or an accounting entry if the
# corrected value is zero.
if svl_to_vacuum.currency_id.is_zero(corrected_value):
continue
corrected_value = svl_to_vacuum.currency_id.round(corrected_value)

corrected_value = svl_to_vacuum.currency_id.round(corrected_value)
move = svl_to_vacuum.stock_move_id
vals = {
"product_id": svl_to_vacuum.product_id.id,
"value": corrected_value,
"unit_cost": 0,
"quantity": 0,
"remaining_qty": 0,
"stock_move_id": move.id,
"company_id": move.company_id.id,
"description": "Revaluation of %s (negative inventory)"
% move.picking_id.name
or move.name,
"stock_valuation_layer_id": svl_to_vacuum.id,
}
vacuum_svl = self.env["stock.valuation.layer"].sudo().create(vals)

if self.valuation != "real_time":
continue
as_svls.append((vacuum_svl, svl_to_vacuum))
move = svl_to_vacuum.stock_move_id
new_svl_vals = (
new_svl_vals_real_time
if product.valuation == "real_time"
else new_svl_vals_manual
)
new_svl_vals.append(
{
"product_id": product.id,
"value": corrected_value,
"unit_cost": 0,
"quantity": 0,
"remaining_qty": 0,
"stock_move_id": move.id,
"company_id": move.company_id.id,
"description": "Revaluation of %s (negative inventory)"
% (move.picking_id.name or move.name),
"stock_valuation_layer_id": svl_to_vacuum.id,
}
)
if product.valuation == "real_time":
current_real_time_svls |= svl_to_vacuum
real_time_svls_to_vacuum |= current_real_time_svls
ValuationLayer.create(new_svl_vals_manual)
vacuum_svls = ValuationLayer.create(new_svl_vals_real_time)

# If some negative stock were fixed, we need to recompute the standard price.
product = self.with_company(company.id)
if product.cost_method == "average" and not float_is_zero(
product.quantity_svl, precision_rounding=self.uom_id.rounding
):
product.sudo().with_context(disable_auto_svl=True).write(
{"standard_price": product.value_svl / product.quantity_svl}
)

self.env["stock.valuation.layer"].browse(
x[0].id for x in as_svls
)._validate_accounting_entries()
for product in self:
product = product.with_company(company.id)
if product.cost_method == "average" and not float_is_zero(
product.quantity_svl, precision_rounding=product.uom_id.rounding
):
product.sudo().with_context(disable_auto_svl=True).write(
{"standard_price": product.value_svl / product.quantity_svl}
)

for vacuum_svl, svl_to_vacuum in as_svls:
self._create_fifo_vacuum_anglo_saxon_expense_entry(
vacuum_svl, svl_to_vacuum
)
vacuum_svls._validate_accounting_entries()
self._create_fifo_vacuum_anglo_saxon_expense_entries(
zip(vacuum_svls, real_time_svls_to_vacuum)
)

if not hasattr(ProductProduct, "_run_fifo_vacuum_original"):
ProductProduct._run_fifo_vacuum_original = ProductProduct._run_fifo_vacuum
Expand Down

0 comments on commit 869af96

Please sign in to comment.