Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
5 changes: 4 additions & 1 deletion openupgrade_scripts/apriori.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@
"pos_sale_order_load": "pos_sale",
# OCA/product-attribute
"stock_account_product_cost_security": "product_cost_security",
"product_sale_tax_price_included": "account",
# OCA/queue
"queue_job_context": "queue_job",
# OCA/server-tools
"base_jsonify": "jsonifier",
# OCA/server-ux
Expand All @@ -106,7 +109,7 @@
"stock_inventory_valuation_pivot": "stock_account",
# OCA/stock-logistics-warehouse
"stock_inventory_exclude_sublocation": "stock",
"stock_orderpoint_manual_procurement": "stock",
"stock_putaway_method": "stock_putaway_hook",
# OCA/stock-logistics-workflow
"stock_deferred_assign": "stock",
"stock_move_assign_picking_hook": "stock",
Expand Down
71 changes: 35 additions & 36 deletions openupgrade_scripts/scripts/hr_expense/15.0.2.0/post-migration.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,44 +14,43 @@ def _fill_payment_state(env):
openupgrade.logged_query(
env.cr,
"""
UPDATE account_move_line
SET exclude_from_invoice_tab=(coalesce(quantity, 0) = 0)
WHERE expense_id IS NOT NULL
UPDATE account_move_line AS aml
SET exclude_from_invoice_tab = true
FROM account_account AS aa
INNER JOIN account_account_type AS aat ON
aa.user_type_id = aat.id
WHERE
aml.account_id = aa.id AND
aml.expense_id IS NOT NULL AND
aat.type = 'payable'
""",
)
# Recompute payment_state for the moves associated to the expenses, as on
# v14 these ones were not computed being of type `entry`, which changes now
# on v15 if the method `_payment_state_matters` returns True, which is the
# case for the expense moves
for move in env["hr.expense.sheet"].search([]).account_move_id:
# Extracted and adapted from _compute_amount() in account.move
new_pmt_state = "not_paid" if move.move_type != "entry" else False
total_to_pay = total_residual = 0.0
for line in move.line_ids:
if line.account_id.user_type_id.type in ("receivable", "payable"):
total_to_pay += line.balance
total_residual += line.amount_residual
currencies = move._get_lines_onchange_currency().currency_id
currency = currencies if len(currencies) == 1 else move.company_id.currency_id
if currency.is_zero(move.amount_residual):
reconciled_payments = move._get_reconciled_payments()
if not reconciled_payments or all(
payment.is_matched for payment in reconciled_payments
):
new_pmt_state = "paid"
else:
new_pmt_state = move._get_invoice_in_payment_state()
elif currency.compare_amounts(total_to_pay, total_residual) != 0:
new_pmt_state = "partial"
openupgrade.logged_query(
env.cr,
"""
UPDATE account_move
SET payment_state = %s
WHERE id = %s
""",
(new_pmt_state, move.id),
)
# Recompute several fields (always_tax_exigible, amount_residual,
# amount_residual_signed, amount_untaxed, amount_untaxed_signed,
# payment_state) for the moves associated to the expenses, as on v14 these
# ones were not computed being of type `entry`, which changes now on v15
# if the method `_payment_state_matters` returns True, which is the case
# for the expense moves.
#
# The compute MUST run under env.protecting(): called bare, every
# assignment inside _compute_amount() goes through Field.__set__ ->
# write(), which triggers _inverse_amount_total and REWRITES the
# debit/credit of reconciled 2-line expense moves (zeroing them when the
# recomputed amount_total is 0). Disabling the reconciliation check (the
# 4ac3a169 approach) silently persists that corruption instead of
# crashing on it. Protected, the assignments land in the cache and
# flush() persists them by SQL: amounts follow the lines, never the
# other way around, and no lock-date or reconciliation check fires.
AccountMove = env["account.move"]
moves = AccountMove.with_context(active_test=False, tracking_disable=True).search(
[("line_ids.expense_id", "!=", False)]
)
fields_amount = [
f for f in AccountMove._fields.values() if f.compute == "_compute_amount"
]
with env.protecting(fields_amount, moves):
moves._compute_amount()
AccountMove.flush([f.name for f in fields_amount], moves)


@openupgrade.migrate()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import logging

from openupgradelib import openupgrade

_logger = logging.getLogger(__name__)

# Kept small so each browse+compute+flush cycle stays within the container
# memory budget: the cache is dropped between chunks (invalidate_cache), so
# peak memory is one chunk, not the whole table. No commit inside the loop:
# @openupgrade.migrate() wraps the script in a savepoint (a commit would
# destroy it -> RELEASE SAVEPOINT crashes on exit) and the module upgrade
# must stay transactional; flushed rows live in the module's transaction.
CHUNK = 5000


def _batched_recompute(env, model_name, fname):
"""Recompute one stored computed field over the whole table, in
memory-safe chunks, using the model's OWN compute method via the ORM.

The column was pre-created empty in pre-migration to skip the ORM's
whole-table mass init (which OOM-kills the upgrade). Here we fill it: the
compute is triggered through the ORM (add_to_compute + flush), so the
vendor's real compute logic runs — we never need to read or reimplement
it. Self-contained: the field is created AND filled within this same hop.
"""
model = env[model_name].with_context(active_test=False, prefetch_fields=False)
field = model._fields[fname]
env.cr.execute('SELECT id FROM "%s" ORDER BY id' % model._table)
ids = [row[0] for row in env.cr.fetchall()]
total = len(ids)
_logger.info(
"recompute %s.%s over %s rows (chunk %s)", model_name, fname, total, CHUNK
)
for start in range(0, total, CHUNK):
recs = model.browse(ids[start : start + CHUNK])
env.add_to_compute(field, recs)
recs.flush([fname], recs)
recs.invalidate_cache()
if start and start % (CHUNK * 20) == 0:
_logger.info(" ... %s/%s", start, total)
_logger.info("recompute %s.%s done", model_name, fname)


@openupgrade.migrate()
def migrate(env, version):
_batched_recompute(env, "account.move.line", "l10n_pt_account_expected_balance")
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from openupgradelib import openupgrade


@openupgrade.migrate()
def migrate(env, version):
# Some 14.0 databases already carry l10n_pt_vat_adjustment_norm_id (the
# 15.0 target name) while the old vat_adjustment_norm_id duplicate also
# exists on account_move. The module's own pre-migration then calls
# openupgrade.rename_fields() old -> new and fails because the target
# exists. Drop the old duplicate first, but only when it is provably
# empty. Named pre-00-* so it sorts — and therefore runs — before the
# module's own pre-migration.py (same-version stage scripts execute in
# basename order).
cr = env.cr
if not (
openupgrade.column_exists(cr, "account_move", "vat_adjustment_norm_id")
and openupgrade.column_exists(
cr, "account_move", "l10n_pt_vat_adjustment_norm_id"
)
):
return
cr.execute(
"SELECT count(*) FROM account_move WHERE vat_adjustment_norm_id IS NOT NULL"
)
old_non_null = cr.fetchone()[0]
if old_non_null:
raise RuntimeError(
"Refusing to drop account_move.vat_adjustment_norm_id: "
"%s non-null rows" % old_non_null
)
openupgrade.logged_query(
cr,
"""
DELETE FROM ir_model_data
WHERE model = 'ir.model.fields'
AND res_id IN (
SELECT id FROM ir_model_fields
WHERE model = 'account.move'
AND name = 'vat_adjustment_norm_id'
)
""",
)
openupgrade.logged_query(
cr,
"""
DELETE FROM ir_model_fields
WHERE model = 'account.move'
AND name = 'vat_adjustment_norm_id'
""",
)
openupgrade.logged_query(
cr, "ALTER TABLE account_move DROP COLUMN vat_adjustment_norm_id"
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from openupgradelib import openupgrade


@openupgrade.migrate()
def migrate(env, version):
# The 15.0.5.0.0 upgrade adds the stored computed field
# account_move_line.l10n_pt_account_expected_balance. Creating the column
# here makes the ORM skip its whole-table "Storing computed values" pass
# (millions of rows), which cannot fit in memory during `-u all`. The
# field is recomputed in controlled batches at the target version.
openupgrade.logged_query(
env.cr,
"""
ALTER TABLE account_move_line
ADD COLUMN IF NOT EXISTS l10n_pt_account_expected_balance numeric
""",
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import logging

from openupgradelib import openupgrade

_logger = logging.getLogger(__name__)

# Kept small so each browse+compute+flush cycle stays within the container
# memory budget: the cache is dropped between chunks (invalidate_cache), so
# peak memory is one chunk, not the whole table. No commit inside the loop:
# @openupgrade.migrate() wraps the script in a savepoint (a commit would
# destroy it -> RELEASE SAVEPOINT crashes on exit) and the module upgrade
# must stay transactional; flushed rows live in the module's transaction.
CHUNK = 5000


def _batched_recompute(env, model_name, fname):
"""Recompute one stored computed field over the whole table, in
memory-safe chunks, using the model's OWN compute method via the ORM.

The column was pre-created empty in pre-migration to skip the ORM's
whole-table mass init (which OOM-kills the upgrade). Here we fill it: the
compute is triggered through the ORM (add_to_compute + flush), so the
vendor's real compute logic runs — we never need to read or reimplement
it. Self-contained: the field is created AND filled within this same hop.
"""
model = env[model_name].with_context(active_test=False, prefetch_fields=False)
field = model._fields[fname]
env.cr.execute('SELECT id FROM "%s" ORDER BY id' % model._table)
ids = [row[0] for row in env.cr.fetchall()]
total = len(ids)
_logger.info(
"recompute %s.%s over %s rows (chunk %s)", model_name, fname, total, CHUNK
)
for start in range(0, total, CHUNK):
recs = model.browse(ids[start : start + CHUNK])
env.add_to_compute(field, recs)
recs.flush([fname], recs)
recs.invalidate_cache()
if start and start % (CHUNK * 20) == 0:
_logger.info(" ... %s/%s", start, total)
_logger.info("recompute %s.%s done", model_name, fname)


@openupgrade.migrate()
def migrate(env, version):
_batched_recompute(env, "stock.picking", "l10n_pt_is_national")
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from openupgradelib import openupgrade


@openupgrade.migrate()
def migrate(env, version):
# The 15.0.5.0.0 upgrade adds the stored computed field
# stock_picking.l10n_pt_is_national. Creating the column here makes the
# ORM skip its whole-table "Storing computed values" pass (millions of
# rows), which cannot fit in memory during `-u all`. The field is
# recomputed in controlled batches at the target version.
openupgrade.logged_query(
env.cr,
"""
ALTER TABLE stock_picking
ADD COLUMN IF NOT EXISTS l10n_pt_is_national boolean
""",
)
Loading