-
-
Notifications
You must be signed in to change notification settings - Fork 705
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2376 from ForgeFlow/13.0-mig-stock_account-script
[13.0][MIG] stock_account
- Loading branch information
Showing
4 changed files
with
353 additions
and
1 deletion.
There are no files selected for viewing
79 changes: 79 additions & 0 deletions
79
addons/stock_account/migrations/13.0.1.1/openupgrade_analysis_work.txt
This file contains 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,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?? | ||
|
||
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
260
addons/stock_account/migrations/13.0.1.1/post-migration.py
This file contains 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,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", | ||
] | ||
) |
This file contains 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,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) |
This file contains 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