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
2 changes: 1 addition & 1 deletion docsource/modules170-180.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1030,7 +1030,7 @@ Module coverage 17.0 -> 18.0
+---------------------------------------------------+----------------------+-------------------------------------------------+
| spreadsheet_dashboard_website_sale_slides | | |
+---------------------------------------------------+----------------------+-------------------------------------------------+
| stock | | |
| stock | Done | |
+---------------------------------------------------+----------------------+-------------------------------------------------+
| stock_account | | |
+---------------------------------------------------+----------------------+-------------------------------------------------+
Expand Down
99 changes: 99 additions & 0 deletions openupgrade_scripts/scripts/stock/18.0.1.1/post-migration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Copyright 2025 ForgeFlow S.L. (https://www.forgeflow.com)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from openupgradelib import openupgrade, openupgrade_180


def convert_company_dependent(env):
openupgrade_180.convert_company_dependent(
env, "product.template", "property_stock_inventory"
)
openupgrade_180.convert_company_dependent(
env, "product.template", "property_stock_production"
)
openupgrade_180.convert_company_dependent(env, "product.template", "responsible_id")
openupgrade_180.convert_company_dependent(
env, "res.partner", "property_stock_customer"
)
openupgrade_180.convert_company_dependent(
env, "res.partner", "property_stock_supplier"
)


def _create_default_new_types_for_all_warehouses(env):
# method mainly based on _create_or_update_sequences_and_picking_types()
all_warehouses = env["stock.warehouse"].with_context(active_test=False).search([])
for wh in all_warehouses:
sequence_data = wh._get_sequence_values()
for field in ["qc_type_id", "store_type_id", "xdock_type_id"]:
# choose the next available color for the operation types of this warehouse
all_used_colors = [
res["color"]
for res in env["stock.picking.type"]
.with_context(active_test=False)
.search_read(
[("warehouse_id", "!=", False), ("color", "!=", False)],
["color"],
order="color",
)
]
available_colors = [
zef for zef in range(0, 12) if zef not in all_used_colors
]
color = available_colors[0] if available_colors else 0
# suit for each warehouse: reception, internal, pick, pack, ship
max_sequence = (
env["stock.picking.type"]
.with_context(active_test=False)
.search_read(
[("sequence", "!=", False)],
["sequence"],
limit=1,
order="sequence desc",
)
)
max_sequence = max_sequence and max_sequence[0]["sequence"] or 0
values = wh._get_picking_type_update_values()[field]
create_data, _ = wh._get_picking_type_create_values(max_sequence)
values.update(create_data[field])
sequence = env["ir.sequence"].create(sequence_data[field])
values.update(
warehouse_id=wh.id,
color=color,
sequence_id=sequence.id,
sequence=max_sequence + 1,
company_id=wh.company_id.id,
active=wh.active,
)
# create picking type
picking_type_id = env["stock.picking.type"].create(values).id
# update picking type for warehouse
wh.write({field: picking_type_id})


def _set_inter_company_locations(env):
"""See https://github.com/odoo/odoo/commit/08536d687880ca6d9ad5c37b639c0ad4c2599d74"""
companies = env["res.company"].search([])
if len(companies) > 1:
inter_company_location = env.ref("stock.stock_location_inter_company")
inactive = False
if not inter_company_location.active:
inactive = True
inter_company_location.sudo().write({"active": True})
for company in companies:
company.sudo()._set_per_company_inter_company_locations(
inter_company_location
)
if inactive:
# we leave everything as it was
inter_company_location.sudo().write({"active": False})


@openupgrade.migrate()
def migrate(env, version):
convert_company_dependent(env)
_create_default_new_types_for_all_warehouses(env)
_set_inter_company_locations(env)
openupgrade.load_data(env, "stock", "18.0.1.1/noupdate_changes.xml")
openupgrade.delete_records_safely_by_xml_id(
env, ["stock.property_stock_customer", "stock.property_stock_supplier"]
)
81 changes: 81 additions & 0 deletions openupgrade_scripts/scripts/stock/18.0.1.1/pre-migration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Copyright 2025 ForgeFlow S.L. (https://www.forgeflow.com)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from openupgradelib import openupgrade

_field_renames = [
("stock.move", "stock_move", "location_dest_id", "location_final_id"),
Copy link
Member

Choose a reason for hiding this comment

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

@MiquelRForgeFlow did you test this with a multi step route? Now working on the mrp migration, I very much doubt this is correct.

I think we need to keep location_dest_id what it is, and reconstruct location_final_id from the procurement that cause the move

Copy link
Contributor Author

Choose a reason for hiding this comment

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

But location_dest_id is filled in fill_stock_move_location_dest_id().

Copy link
Member

@hbrunn hbrunn Jun 2, 2025

Choose a reason for hiding this comment

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

yes, but still location_final_id will be the original value of location_dest_id.

Consider a route:

A -> B
B -> C

then we want the v17 move with

{
    'location_src_id': A,
    'location_dest_id': B,
}

to end up in v18 as

{
    'location_src_id': A,
    'location_dest_id': B,
    'location_final_id': C,
}

while now we get

{
    'location_src_id': A,
    'location_dest_id': B,
    'location_final_id': B,
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, then it lacks some script to update the location_final_id at some point in the migration. I suppose it has to check the locations of move_dest_ids, right? Do you have any concrete idea?

Copy link
Member

Choose a reason for hiding this comment

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

I'm now working on a function that takes all stock moves from the procurement group, and determines the "greatest" location_dest_id in the sense that there's no other stock move that goes out of this location within the group. will probably need some fine tuning re scrap locations and the like

Copy link
Member

Choose a reason for hiding this comment

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

this is more tricky then it looked like in the beginning, will revisit next week

Copy link
Contributor Author

@MiquelRForgeFlow MiquelRForgeFlow Jun 2, 2025

Choose a reason for hiding this comment

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

UPDATE stock_move sm
SET location_final_id = COALESCE(sr.location_dest_id, sm.location_final_id)
FROM stock_rule sr
WHERE sm.rule_id = sr.id AND sr.location_dest_from_rule

What do you think of this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

See #5088.

Copy link
Member

Choose a reason for hiding this comment

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

I'm still not entirely clear on the correct behavior of this for chained moves, will have to do more code analysis next week

(
"stock.warehouse.orderpoint",
"stock_warehouse_orderpoint",
"qty_to_order",
"qty_to_order_manual",
),
]

_xmlid_renames = [
("stock.stock_location_inter_wh", "stock.stock_location_inter_company"),
]

_new_columns = [
("product.template", "is_storable", "boolean", False),
("stock.move", "location_dest_id", "many2one"),
("stock.rule", "location_dest_from_rule", "boolean", False),
("stock.picking.type", "move_type", "selection", "direct"),
("stock.putaway.rule", "sublocation", "selection", "no"),
]


def fill_product_template_is_storable(env):
openupgrade.logged_query(
env.cr,
"""
UPDATE product_template
SET is_storable = TRUE, type = 'consu'
WHERE type = 'product'""",
)


def fill_stock_move_location_dest_id(env):
openupgrade.logged_query(
env.cr,
"""
UPDATE stock_move sm2
SET location_dest_id = COALESCE(sp.location_dest_id,
spt.default_location_dest_id, sm.location_final_id)
FROM stock_move sm
LEFT JOIN stock_picking sp ON sm.picking_id = sp.id
LEFT JOIN stock_picking_type spt ON sm.picking_type_id = spt.id
WHERE sm2.id = sm.id
""",
)
openupgrade.logged_query(
env.cr,
"""
UPDATE stock_rule sr
SET location_dest_from_rule = TRUE
FROM stock_move sm
WHERE sm.rule_id = sr.id
AND sm.location_dest_id != sm.location_final_id
AND sr.action IN ('pull', 'pull_push')
""",
)


def fill_stock_putaway_rule_sublocation(env):
openupgrade.logged_query(
env.cr,
"""
UPDATE stock_putaway_rule
SET sublocation = 'closest_location'
WHERE storage_category_id is not null""",
)


@openupgrade.migrate()
def migrate(env, version=None):
openupgrade.rename_fields(env, _field_renames)
openupgrade.rename_xmlids(env.cr, _xmlid_renames)
openupgrade.add_columns(env, _new_columns)
fill_product_template_is_storable(env)
fill_stock_move_location_dest_id(env)
fill_stock_putaway_rule_sublocation(env)
139 changes: 139 additions & 0 deletions openupgrade_scripts/scripts/stock/18.0.1.1/upgrade_analysis_work.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
---Models in module 'stock'---
obsolete model stock.assign.serial [transient]
obsolete model stock.scheduler.compute [transient]
new model stock.lot.report [sql_view]
new model stock.replenish.mixin [abstract]
new model stock.scrap.reason.tag
# NOTHING TO DO

---Fields in module 'stock'---
stock / product.template / detailed_type (False) : DEL selection_keys: ['consu', 'product', 'service'], mode: modify
stock / product.template / type (False) : DEL selection_keys: ['consu', 'product', 'service'], mode: modify
stock / product.template / is_storable (boolean) : NEW hasdefault: default
# DONE: pre-migration:: pre-create and fill is_storable and move 'product' to 'consu'

stock / product.template / route_from_categ_ids (many2many): table is now 'False' ('stock.route')
# NOTHING TO DO: related field

stock / product.template / property_stock_inventory (many2one): needs conversion to v18-style company dependent
stock / product.template / property_stock_production (many2one): needs conversion to v18-style company dependent
stock / product.template / responsible_id (many2one) : needs conversion to v18-style company dependent
stock / res.partner / property_stock_customer (many2one): needs conversion to v18-style company dependent
stock / res.partner / property_stock_supplier (many2one): needs conversion to v18-style company dependent
# DONE: post-migration: used openupgrade_180.convert_company_dependent

stock / stock.location / return_location (boolean) : DEL
stock / stock.picking.type / default_location_return_id (many2one): DEL relation: stock.location
# NOTHING TO DO: now is using _can_return() method instead

stock / stock.move / location_dest_id (many2one) : now a function
stock / stock.move / location_final_id (many2one) : NEW relation: stock.location
stock / stock.rule / location_dest_from_rule (boolean): NEW hasdefault: default
# DONE: pre-migration: rename location_dest_id to location_final_id, pre-create and fill location_dest_id and location_dest_from_rule

stock / stock.move / never_product_template_attribute_value_ids (many2many): NEW relation: product.template.attribute.value
# NOTHING TO DO: new technical feature

stock / stock.move.line / product_category_name (char) : not stored anymore
# NOTHING TO DO

stock / stock.picking / search_date_category (selection): NEW selection_keys: ['after', 'before', 'day_1', 'day_2', 'today', 'yesterday'], stored: False
stock / stock.picking / shipping_weight (float) : previously in module stock_delivery
stock_delivery / stock.picking / shipping_weight (float) : not a function anymore
stock / stock.picking / weight_bulk (float) : previously in module stock_delivery
# NOTHING TO DO: not stored

stock / stock.picking.type / _order : _order is now 'is_favorite desc, sequence, id' ('sequence, id')
# NOTHING TO DO: is_favorite is not stored

stock / stock.picking.type / default_location_dest_id (many2one): now required
stock / stock.picking.type / default_location_src_id (many2one): now required
# NOTHING TO DO? (not sure if we can assure it someway)

stock / stock.picking.type / favorite_user_ids (many2many) : NEW relation: res.users
# NOTHING TO DO: new feature

stock / stock.picking.type / move_type (selection) : NEW required, selection_keys: ['direct', 'one'], hasdefault: default
# DONE: pre-migration: pre-create and fill it with default 'direct'

stock / stock.picking.type / show_reserved (boolean) : DEL
# NOTHING TO DO: obsolete, see https://github.com/odoo/odoo/commit/54a795bf11d1ef5d791e587b0c9e4d260b97a79d

stock / stock.putaway.rule / sublocation (selection) : NEW selection_keys: ['closest_location', 'last_used', 'no'], hasdefault: default
# DONE: pre-migration: pre-create and fill it ('no' by default, 'closest_location' if storage_category_id is not null)

stock / stock.quant.package / location_id (many2one) : not a function anymore
# NOTHING TO DO: not sure why marked this way, it's the same in both v17 and v18

stock / stock.quant.package / shipping_weight (float) : previously in module stock_delivery
# NOTHING TO DO

stock / stock.rule / push_domain (char) : NEW
# NOTHING TO DO: new feature

stock / stock.scrap / scrap_reason_tag_ids (many2many): NEW relation: stock.scrap.reason.tag
stock / stock.scrap.reason.tag / color (char) : NEW hasdefault: default
stock / stock.scrap.reason.tag / name (char) : NEW required
stock / stock.scrap.reason.tag / sequence (integer) : NEW hasdefault: default
# NOTHING TO DO: new feature

stock / stock.warehouse / qc_type_id (many2one) : NEW relation: stock.picking.type
stock / stock.warehouse / store_type_id (many2one) : NEW relation: stock.picking.type
stock / stock.warehouse / xdock_type_id (many2one) : NEW relation: stock.picking.type
# DONE: post-migration: created / updated default picking types for all warehouses

stock / stock.warehouse.orderpoint / qty_to_order (float) : not stored anymore
stock / stock.warehouse.orderpoint / qty_to_order (float) : now a function
stock / stock.warehouse.orderpoint / qty_to_order_manual (float) : NEW
# DONE: pre-migration: rename qty_to_order to qty_to_order_manual

---XML records in module 'stock'---
NEW digest.tip: stock.digest_tip_stock_1
NEW ir.actions.act_window: stock.action_get_picking_type_ready_moves
NEW ir.actions.act_window: stock.action_inventory_at_date
NEW ir.actions.act_window: stock.action_lot_report
NEW ir.actions.act_window: stock.action_picking_tree_graph
# NOTHING TO DO

ir.actions.act_window: stock.stock_picking_action_picking_type (deleted domain)
# NOTHING TO DO: domain was already empty

DEL ir.actions.act_window: stock.act_assign_serial_numbers
DEL ir.actions.act_window: stock.action_procurement_compute
NEW ir.actions.server: stock.action_install_barcode
NEW ir.actions.server: stock.action_print_labels
NEW ir.actions.server: stock.click_dashboard_graph
NEW ir.actions.server: stock.method_action_picking_tree_incoming
NEW ir.actions.server: stock.method_action_picking_tree_internal
NEW ir.actions.server: stock.method_action_picking_tree_outgoing
NEW ir.actions.server: stock.stock_split_picking
NEW ir.config_parameter: stock.barcode_separator (noupdate)
NEW ir.model.access: stock.access_stock_inventory_adjustment_name_manager
NEW ir.model.access: stock.access_stock_inventory_adjustment_name_user
NEW ir.model.access: stock.access_stock_location_all_user
NEW ir.model.access: stock.access_stock_lot_report
NEW ir.model.access: stock.access_stock_scrap_reason_tag_manager
NEW ir.model.access: stock.access_stock_scrap_reason_tag_user
NEW ir.model.access: stock.access_update_product_attribute_value_stock_manager
DEL ir.model.access: stock.access_stock_assign_serial
DEL ir.model.access: stock.access_stock_inventory_adjustment_name
DEL ir.model.access: stock.access_stock_scheduler_compute
NEW ir.model.constraint: stock.constraint_stock_scrap_reason_tag_name_uniq
DEL ir.ui.menu: stock.menu_reordering_rules_config
NEW ir.ui.view: stock.help_message_template
NEW ir.ui.view: stock.product_template_search_view_inherit_stock
NEW ir.ui.view: stock.search_customer_lot_filter
NEW ir.ui.view: stock.stock_lot_customer_report_view_list
DEL ir.ui.view: stock.view_assign_serial_numbers
DEL ir.ui.view: stock.view_procurement_compute_wizard
DEL res.groups: stock.group_stock_picking_wave
DEL res.groups: stock.group_stock_storage_categories
# NOTHING TO DO

DEL ir.property: stock.property_stock_customer (noupdate)
DEL ir.property: stock.property_stock_supplier (noupdate)
# DONE: post-migration: safely delete records

NEW stock.location: stock.stock_location_inter_company (noupdate)
DEL stock.location: stock.stock_location_inter_wh (noupdate)
# DONE: pre-migration: rename xmlid