Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
---Models in module 'stock_account'---
new model stock.valuation.layer
# DONE: post-migration: try to use data from product.price.history in some way (?)

---Fields in module 'stock_account'---
stock_account / account.move.line / is_anglo_saxon_line (boolean) : NEW
# TODO (?): is there a way to know which lines are anglosaxon??
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we could check if aml.company_id.anglo_saxon_accounting is true


stock_account / product.product / valuation (char) : selection_keys is now 'function' ('False')
stock_account / product.product / valuation (char) : type is now 'selection' ('char')
stock_account / product.product / cost_method (char) : selection_keys is now 'function' ('False')
stock_account / product.product / cost_method (char) : type is now 'selection' ('char')
stock_account / product.template / cost_method (char) : not a function anymore
stock_account / product.template / cost_method (char) : now related
stock_account / product.template / cost_method (char) : selection_keys is now 'function' ('False')
stock_account / product.template / cost_method (char) : type is now 'selection' ('char')
stock_account / product.template / valuation (char) : not a function anymore
stock_account / product.template / valuation (char) : now related
stock_account / product.template / valuation (char) : selection_keys is now 'function' ('False')
stock_account / product.template / valuation (char) : type is now 'selection' ('char')
# NOTHING TO DO: they are non stored fields

stock_account / product.template / property_cost_method (selection): DEL selection_keys: ['average', 'fifo', 'standard']
stock_account / product.template / property_stock_account_input (many2one): DEL relation: account.account
stock_account / product.template / property_stock_account_output (many2one): DEL relation: account.account
stock_account / product.template / property_valuation (selection): DEL selection_keys: ['manual_periodic', 'real_time']
# NOTHING TO DO: they use now the same fields from product.category instead

stock_account / stock.move / remaining_qty (float) : DEL
stock_account / stock.move / remaining_value (float) : DEL
stock_account / stock.move / value (float) : DEL
stock_account / stock.valuation.layer / remaining_qty (float) : NEW
stock_account / stock.valuation.layer / remaining_value (float) : NEW
stock_account / stock.valuation.layer / value (float) : NEW
# NOTHING TO DO: the relation between moves and valuation layers is new
# TODO (?): if the link with the stock move is found, then fill values

stock_account / stock.valuation.layer / account_move_id (many2one) : NEW relation: account.move
stock_account / stock.valuation.layer / company_id (many2one) : NEW relation: res.company, required
stock_account / stock.valuation.layer / description (char) : NEW
stock_account / stock.valuation.layer / product_id (many2one) : NEW relation: product.product, required
stock_account / stock.valuation.layer / quantity (float) : NEW
stock_account / stock.valuation.layer / stock_move_id (many2one) : NEW relation: stock.move
stock_account / stock.valuation.layer / unit_cost (float) : NEW
stock_account / stock.valuation.layer / stock_valuation_layer_id (many2one): NEW relation: stock.valuation.layer
stock_account / account.move / stock_valuation_layer_ids (one2many): NEW relation: stock.valuation.layer
stock_account / product.product / stock_valuation_layer_ids (one2many): NEW relation: stock.valuation.layer
stock_account / stock.move / stock_valuation_layer_ids (one2many): NEW relation: stock.valuation.layer
stock_account / stock.valuation.layer / stock_valuation_layer_ids (one2many): NEW relation: stock.valuation.layer
# DONE: post-migration: handle the new model (try to use data from product.price.history in some way?)

---XML records in module 'stock_account'---
NEW ir.actions.act_window: stock_account.stock_valuation_layer_action
DEL ir.actions.act_window: stock_account.product_valuation_action
NEW ir.model.access: stock_account.access_stock_valuation_layer
NEW ir.rule: stock_account.stock_valuation_layer_company_rule
NEW ir.ui.view: stock_account.product_template_tree_view
NEW ir.ui.view: stock_account.stock_valuation_layer_form
NEW ir.ui.view: stock_account.stock_valuation_layer_picking
NEW ir.ui.view: stock_account.stock_valuation_layer_search
NEW ir.ui.view: stock_account.stock_valuation_layer_tree
NEW ir.ui.view: stock_account.view_inventory_tree
NEW ir.ui.view: stock_account.view_stock_quant_tree_editable_inherit
NEW ir.ui.view: stock_account.view_stock_quant_tree_inherit
DEL ir.ui.view: stock_account.view_move_tree_valuation_at_date
DEL ir.ui.view: stock_account.view_stock_account_aml
DEL ir.ui.view: stock_account.view_stock_product_tree2
DEL ir.ui.view: stock_account.view_stock_quantity_history
# NOTHING TO DO

NEW ir.ui.view: stock_account.product_product_normal_form_view_inherit
DEL ir.ui.view: stock_account.product_normal_form_view_inherit
# DONE: pre-migration: xmlid renamed (to avoid a xpath issue)

DEL ir.property: stock_account.default_cost_method (noupdate)
DEL ir.property: stock_account.default_valuation (noupdate)
DEL ir.property: stock_account.property_stock_account_input_prd (noupdate)
DEL ir.property: stock_account.property_stock_account_output_prd (noupdate)
# DONE: post-migration: try to delete
260 changes: 260 additions & 0 deletions addons/stock_account/migrations/13.0.1.1/post-migration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
# Copyright 2020 ForgeFlow <http://www.forgeflow.com>
# Copyright 2020 Andrii Skrypka
# Copyright 2021 Tecnativa - Carlos Dauden
# Copyright 2021 Tecnativa - Sergio Teruel
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).

import logging

from openupgradelib import openupgrade
from odoo import _
from odoo.tools.float_utils import float_compare, float_is_zero, float_round
from odoo.addons.base.models.ir_model import query_insert

_logger = logging.getLogger(__name__)

# Declare global variant to avoid that it is passed between methods
precision_price = 0


def _prepare_common_svl_vals(move, product):
return {
"create_uid": move["write_uid"],
"create_date": move["date"],
"write_uid": move["write_uid"],
"write_date": move["date"],
"stock_move_id": move["id"],
"company_id": move["company_id"],
"product_id": move["product_id"],
"description": move["reference"] and "%s - %s" % (move["reference"], product.name) or product.name,
"value": 0.0,
"unit_cost": 0.0,
"remaining_qty": 0.0,
"remaining_value": 0.0,
"quantity": 0.0,
"old_product_price_history_id": None,
"account_move_id": move["account_move_id"],
}


def _prepare_in_svl_vals(move, quantity, unit_cost, product, is_dropship):
vals = _prepare_common_svl_vals(move, product)
vals.update({
"value": float_round(unit_cost * quantity, precision_digits=precision_price),
"unit_cost": unit_cost,
"quantity": quantity,
})
if product.cost_method in ("average", "fifo") and not is_dropship:
vals["remaining_qty"] = quantity
vals["remaining_value"] = vals["value"]
return vals


def _prepare_out_svl_vals(move, quantity, unit_cost, product):
# Quantity is negative for out valuation layers.
quantity = -quantity
vals = _prepare_common_svl_vals(move, product)
vals.update({
"value": float_round(unit_cost * quantity, precision_digits=precision_price),
"unit_cost": unit_cost,
"quantity": quantity,
"remaining_qty": 0.0,
"remaining_value": 0.0,
})
return vals


def _prepare_man_svl_vals(price_history_rec, previous_price, quantity, company, product):
diff = price_history_rec["cost"] - previous_price
value = float_round(diff * quantity, precision_digits=precision_price)
svl_vals = {
"create_uid": price_history_rec["write_uid"],
"create_date": price_history_rec["datetime"],
"write_uid": price_history_rec["write_uid"],
"write_date": price_history_rec["datetime"],
"stock_move_id": None,
"company_id": company.id,
"product_id": product.id,
"description": _("Product value manually modified (from %s to %s)"
) % (previous_price, price_history_rec["cost"]),
"value": value,
"unit_cost": 0.0,
"remaining_qty": 0.0,
"remaining_value": 0.0,
"quantity": 0.0,
"old_product_price_history_id": price_history_rec["id"],
"account_move_id": price_history_rec["account_move_id"],
}
return svl_vals


def get_product_price_history(env, company_id, product_id):
env.cr.execute("""
WITH account_move_rel AS (
SELECT id, create_date
FROM (
SELECT id, create_date, COUNT(*) OVER(PARTITION BY create_date) AS qty
FROM account_move
WHERE stock_move_id IS NULL
) foo
WHERE qty = 1
)
SELECT pph.id, pph.company_id, pph.product_id, pph.datetime, pph.cost, rel.id AS account_move_id,
pph.create_uid, pph.create_date, pph.write_uid, pph.write_date
FROM product_price_history pph
LEFT JOIN account_move_rel rel ON rel.create_date = pph.create_date
WHERE pph.company_id = %s AND pph.product_id = %s
ORDER BY pph.datetime, pph.id
""", (company_id, product_id))
return env.cr.dictfetchall()


def get_stock_moves(env, company_id, product_id):
env.cr.execute("""
WITH account_move_rel AS (
SELECT id, stock_move_id
FROM (
SELECT id, stock_move_id, COUNT(*) OVER(PARTITION BY stock_move_id) AS qty
FROM account_move
WHERE stock_move_id IS NOT NULL
) foo
WHERE qty = 1
)
SELECT sm.id, sm.company_id, sm.product_id, sm.date, sm.product_qty, sm.reference,
COALESCE(sm.price_unit, 0.0) AS price_unit, rel.id AS account_move_id,
sm.create_uid, sm.create_date, sm.write_uid, sm.write_date,
CASE WHEN (sl.usage <> 'internal' AND (sl.usage <> 'transit' OR sl.company_id <> sm.company_id))
AND (sld.usage = 'internal' OR (sld.usage = 'transit' AND sld.company_id = sm.company_id))
THEN 'in'
WHEN (sl.usage = 'internal' OR (sl.usage = 'transit' AND sl.company_id = sm.company_id))
AND (sld.usage <> 'internal' AND (sld.usage <> 'transit' OR sld.company_id <> sm.company_id))
THEN 'out'
WHEN sl.usage = 'supplier' AND sld.usage = 'customer' THEN 'dropship'
WHEN sl.usage = 'customer' AND sld.usage = 'supplier' THEN 'dropship_return'
ELSE 'other'
END AS move_type
FROM stock_move sm
LEFT JOIN stock_location sl ON sl.id = sm.location_id
LEFT JOIN stock_location sld ON sld.id = sm.location_dest_id
LEFT JOIN account_move_rel rel ON rel.stock_move_id = sm.id
WHERE sm.company_id = %s AND sm.product_id = %s AND state = 'done'
ORDER BY sm.date, sm.id
""", (company_id, product_id))
return env.cr.dictfetchall()


@openupgrade.logging()
def generate_stock_valuation_layer(env):
openupgrade.logged_query(
env.cr, """
ALTER TABLE stock_valuation_layer
ADD COLUMN old_product_price_history_id integer""",
)
company_obj = env["res.company"]
product_obj = env["product.product"]
# Needed to modify global variable
global precision_price
precision_price = env["decimal.precision"].precision_get("Product Price")
precision_uom = env["decimal.precision"].precision_get(
"Product Unit of Measure"
)
companies = company_obj.search([])
products = product_obj.with_context(active_test=False).search([("type", "in", ("product", "consu"))])
all_svl_list = []
for product in products:
for company in companies:
history_lines = get_product_price_history(env, company.id, product.id)
moves = get_stock_moves(env, company.id, product.id)
svl_in_vals_list = []
svl_out_vals_list = []
svl_man_vals_list = []
svl_in_index = 0
h_index = 0
previous_price = 0.0
previous_qty = 0.0
for move in moves:
is_dropship = True if move["move_type"] in ("dropship", "dropship_return") else False
if product.cost_method in ("average", "standard"):
# useless for Fifo because we have price unit in stock.move
# Add manual adjusts
have_qty = not float_is_zero(previous_qty, precision_digits=precision_uom)
while h_index < len(history_lines) and history_lines[h_index]["datetime"] < move["date"]:
price_history_rec = history_lines[h_index]
if float_compare(price_history_rec["cost"], previous_price, precision_digits=precision_price):
if have_qty:
svl_vals = _prepare_man_svl_vals(
price_history_rec, previous_price, previous_qty, company, product)
svl_man_vals_list.append(svl_vals)
previous_price = price_history_rec["cost"]
h_index += 1
# Add in svl
if move["move_type"] == "in" or is_dropship:
total_qty = previous_qty + move["product_qty"]
# TODO: is needed vaccum if total_qty is negative?
if float_is_zero(total_qty, precision_digits=precision_uom):
previous_price = move["price_unit"]
else:
previous_price = float_round(
(previous_price * previous_qty + move["price_unit"] * move["product_qty"]) / total_qty,
precision_digits=precision_price)
svl_vals = _prepare_in_svl_vals(
move, move["product_qty"], move["price_unit"], product, is_dropship)
svl_in_vals_list.append(svl_vals)
previous_qty = total_qty
# Add out svl
if move["move_type"] == "out" or is_dropship:
qty = move["product_qty"]
if product.cost_method in ("average", "fifo") and not is_dropship:
# Reduce remaininig qty in svl of type "in"
while qty > 0 and svl_in_index < len(svl_in_vals_list):
if svl_in_vals_list[svl_in_index]["remaining_qty"] >= qty:
candidate_cost = (svl_in_vals_list[svl_in_index]["remaining_value"] /
svl_in_vals_list[svl_in_index]["remaining_qty"])
svl_in_vals_list[svl_in_index]["remaining_qty"] -= qty
svl_in_vals_list[svl_in_index]["remaining_value"] = float_round(
candidate_cost * svl_in_vals_list[svl_in_index]["remaining_qty"],
precision_digits=precision_price)
qty = 0
else:
qty -= svl_in_vals_list[svl_in_index]["remaining_qty"]
svl_in_vals_list[svl_in_index]["remaining_qty"] = 0.0
svl_in_vals_list[svl_in_index]["remaining_value"] = 0.0
svl_in_index += 1
if product.cost_method == 'fifo':
svl_vals = _prepare_out_svl_vals(
move, move["product_qty"], move["price_unit"], product)
else:
svl_vals = _prepare_out_svl_vals(
move, move["product_qty"], previous_price, product)
svl_out_vals_list.append(svl_vals)
previous_qty -= move["product_qty"]
# Add manual adjusts after last move
if product.cost_method in ("average", "standard") and not float_is_zero(
previous_qty, precision_digits=precision_uom):
# useless for Fifo because we have price unit on product form
while h_index < len(history_lines):
price_history_rec = history_lines[h_index]
if float_compare(price_history_rec["cost"], previous_price, precision_digits=precision_price):
svl_vals = _prepare_man_svl_vals(
price_history_rec, previous_price, previous_qty, company, product)
svl_man_vals_list.append(svl_vals)
previous_price = price_history_rec["cost"]
h_index += 1
all_svl_list.extend(svl_in_vals_list + svl_out_vals_list + svl_man_vals_list)
if all_svl_list:
all_svl_list = sorted(all_svl_list, key=lambda k: (k["create_date"]))
_logger.info("To create {} svl records".format(len(all_svl_list)))
query_insert(env.cr, "stock_valuation_layer", all_svl_list)


@openupgrade.migrate()
def migrate(env, version):
generate_stock_valuation_layer(env)
openupgrade.delete_records_safely_by_xml_id(
env, [
"stock_account.default_cost_method",
"stock_account.default_valuation",
"stock_account.property_stock_account_input_prd",
"stock_account.property_stock_account_output_prd",
]
)
13 changes: 13 additions & 0 deletions addons/stock_account/migrations/13.0.1.1/pre-migration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright 2020 ForgeFlow <http://www.forgeflow.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openupgradelib import openupgrade

_xmlid_renames = [
('stock_account.product_normal_form_view_inherit',
'stock_account.product_product_normal_form_view_inherit'),
]


@openupgrade.migrate()
def migrate(env, version):
openupgrade.rename_xmlids(env.cr, _xmlid_renames)
2 changes: 1 addition & 1 deletion odoo/openupgrade/doc/source/modules120-130.rst
Original file line number Diff line number Diff line change
Expand Up @@ -597,7 +597,7 @@ missing in the new release are marked with |del|.
+----------------------------------------------+-------------------------------------------------+
|stock | Done |
+----------------------------------------------+-------------------------------------------------+
|stock_account | |
|stock_account | Done |
+----------------------------------------------+-------------------------------------------------+
|stock_dropshipping | |
+----------------------------------------------+-------------------------------------------------+
Expand Down