-
-
Notifications
You must be signed in to change notification settings - Fork 796
[13.0][MIG] stock_account #2376
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
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
278ea79
[MIG] stock_account
MiquelRForgeFlow 64f6dcf
[MIG] stock_account: generate Stock valuation layers according to Sto…
kos94ok-3D d695817
[MIG] stock_account: generate Stock valuation layers according to Sto…
carlosdauden 83ae489
[IMP] stock_account: add account_move_id information
MiquelRForgeFlow b463aec
[IMP] stock_account: in fifo, use price_unit of move in out svl
AaronHForgeFlow 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
79 changes: 79 additions & 0 deletions
79
addons/stock_account/migrations/13.0.1.1/openupgrade_analysis_work.txt
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,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 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,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"], | ||
MiquelRForgeFlow marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| "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, | ||
MiquelRForgeFlow marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| "quantity": 0.0, | ||
| "old_product_price_history_id": None, | ||
| "account_move_id": move["account_move_id"], | ||
MiquelRForgeFlow marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
|
|
||
| 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"], | ||
MiquelRForgeFlow marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| "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") | ||
MiquelRForgeFlow marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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 = [] | ||
MiquelRForgeFlow marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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) | ||
MiquelRForgeFlow marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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 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,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 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
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.
maybe we could check if aml.company_id.anglo_saxon_accounting is true