Compare commits
14 Commits
main
...
f6baf4d1a2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6baf4d1a2 | ||
|
|
81e036cd4a | ||
|
|
aa48543a12 | ||
|
|
9cbb5eaff2 | ||
|
|
ef75d4a5b1 | ||
|
|
d2c4ec6de5 | ||
|
|
e27c309d06 | ||
|
|
8ea746a4d1 | ||
|
|
778730e396 | ||
|
|
969414e649 | ||
|
|
f9d678508b | ||
|
|
55218946c5 | ||
|
|
25fff3da5d | ||
|
|
c59439aa8d |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,4 +1,2 @@
|
||||
final_404_addons
|
||||
migration.log
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
55
README.md
55
README.md
@@ -39,11 +39,10 @@ cd 0k-odoo-upgrade
|
||||
├── lib/
|
||||
│ ├── common.sh # Shared bash functions
|
||||
│ └── python/ # Python utility scripts
|
||||
│ ├── check_views.py # View analysis (pre-migration)
|
||||
│ ├── validate_views.py # View validation (post-migration)
|
||||
│ ├── fix_duplicated_views.py # Fix duplicated views
|
||||
│ ├── dedup_unique_constraints.py # Dedup soon-to-be-unique fields (pre-migration)
|
||||
│ └── cleanup_modules.py # Obsolete module cleanup
|
||||
│ ├── check_views.py # View analysis (pre-migration)
|
||||
│ ├── validate_views.py # View validation (post-migration)
|
||||
│ ├── fix_duplicated_views.py # Fix duplicated views
|
||||
│ └── cleanup_modules.py # Obsolete module cleanup
|
||||
│
|
||||
├── scripts/
|
||||
│ ├── prepare_db.sh # Database preparation before migration
|
||||
@@ -85,8 +84,6 @@ The script performs a **step-by-step migration** between each major version. For
|
||||
3. **Database Preparation** (`scripts/prepare_db.sh`)
|
||||
- Neutralization: disable mail servers and cron jobs
|
||||
- Detection of installed modules missing in the target version
|
||||
- Deduplication of fields that become `UNIQUE` in a traversed version
|
||||
(see [Unique Constraints](#unique-constraints-new_unique_constraints))
|
||||
- View state verification
|
||||
- User confirmation prompt
|
||||
|
||||
@@ -359,48 +356,6 @@ Version scripts have access to functions defined in `lib/common.sh`:
|
||||
| `log_info`, `log_warn`, `log_error` | Logging functions |
|
||||
| `log_step "title"` | Display a section header |
|
||||
|
||||
### Unique Constraints (`new_unique_constraints`)
|
||||
|
||||
Some Odoo versions add a `UNIQUE` constraint on a field that previously allowed
|
||||
duplicates. A well-known case is `utm.source.name` in 16.0. When the migration
|
||||
rebuilds the model, PostgreSQL tries to create the unique index and aborts the
|
||||
whole registry load if the legacy data contains duplicates:
|
||||
|
||||
```
|
||||
psycopg2.errors.UniqueViolation: could not create unique index "utm_source_unique_name"
|
||||
DETAIL: Key (name)=(Webinaire gouvernance (copie)) is duplicated.
|
||||
```
|
||||
|
||||
To prevent this, declare the newly-introduced unique constraints in the
|
||||
`known_changes.yaml` of the version that introduces them, under the
|
||||
`new_unique_constraints` key. Only `model` and `fields` are needed — the table,
|
||||
the column type and the translatable (jsonb) flag are resolved automatically
|
||||
from the ORM:
|
||||
|
||||
```yaml
|
||||
new_unique_constraints:
|
||||
- model: utm.source
|
||||
fields: [name]
|
||||
- model: utm.medium
|
||||
fields: [name]
|
||||
- model: utm.campaign
|
||||
fields: [name]
|
||||
```
|
||||
|
||||
During `prepare_db.sh`, the `dedup_unique_constraints.py` checker aggregates the
|
||||
declarations of every traversed version and, on the **source** database:
|
||||
|
||||
- detects duplicate groups (for translatable/jsonb fields it compares the
|
||||
indexed `en_US` value, not the raw jsonb);
|
||||
- keeps the record with the smallest `id` untouched and renames each other
|
||||
duplicate by appending ` [<id>]` to every language key (jsonb) or to the
|
||||
plain value (varchar) — all foreign keys are preserved;
|
||||
- writes a timestamped report to
|
||||
`/tmp/dedup_unique_constraints_<source_db>_<timestamp>.json`.
|
||||
|
||||
Renamed records are summarized at the end of `migration.log` (see the final
|
||||
`UPGRADE PROCESS ENDED WITH SUCCESS` section).
|
||||
|
||||
### Adding a New Version
|
||||
|
||||
To add support for a new version (e.g., 19.0):
|
||||
@@ -413,8 +368,6 @@ cp versions/18.0/*.sh versions/19.0/
|
||||
# - Change references from ou18 → ou19
|
||||
# - Change the port from -p 8018:8069 → -p 8019:8069
|
||||
# - Add SQL fixes specific to this migration
|
||||
# - Declare any new UNIQUE constraints in versions/19.0/known_changes.yaml
|
||||
# under `new_unique_constraints` (see "Unique Constraints" above)
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
@@ -82,9 +82,6 @@ ou18:
|
||||
- "--log-level=debug"
|
||||
- "--limit-time-cpu=1000000"
|
||||
- "--limit-time-real=1000000"
|
||||
volumes:
|
||||
- /home/stephan/dev/Odoo/0k-odoo-upgrade/versions/18.0/addons/:/opt/odoo/auto/mig:rw
|
||||
- /home/stephan/dev/Odoo/0k-odoo-upgrade/versions/18.0/scripts/:/opt/odoo/auto/upgrade:rw
|
||||
options:
|
||||
workers: 0
|
||||
|
||||
|
||||
@@ -100,27 +100,6 @@ exec_python_script_in_odoo_shell() {
|
||||
run_compose --debug run "$service_name" shell -d "$db_name" --no-http --stop-after-init < "$python_script"
|
||||
}
|
||||
|
||||
# Same as exec_python_script_in_odoo_shell but prepends a Python preamble read
|
||||
# from stdin (heredoc). Useful to inject environment values into the Odoo shell
|
||||
# when the compose wrapper does not reliably propagate host env vars into the
|
||||
# container. The preamble runs before the script body in the same interpreter.
|
||||
#
|
||||
# Usage:
|
||||
# exec_python_script_in_odoo_shell_with_preamble svc db /path/script.py <<'PY'
|
||||
# import os; os.environ['FOO'] = 'bar'
|
||||
# PY
|
||||
exec_python_script_in_odoo_shell_with_preamble() {
|
||||
local service_name="$1"
|
||||
local db_name="$2"
|
||||
local python_script="$3"
|
||||
|
||||
local preamble
|
||||
preamble="$(cat)"
|
||||
|
||||
{ printf '%s\n' "$preamble"; cat "$python_script"; } \
|
||||
| run_compose --debug run "$service_name" shell -d "$db_name" --no-http --stop-after-init
|
||||
}
|
||||
|
||||
# Classifies missing modules into 4 categories based on the known_changes.yaml
|
||||
# files from each traversed version (from ORIGIN_VERSION+1 to FINAL_VERSION).
|
||||
# The following global arrays are populated:
|
||||
@@ -196,50 +175,8 @@ classify_missing_addons() {
|
||||
done
|
||||
}
|
||||
|
||||
# Aggregates the `new_unique_constraints` sections from the known_changes.yaml
|
||||
# files of every traversed version (from ORIGIN_VERSION+1 to FINAL_VERSION) into
|
||||
# a single JSON array printed on stdout, e.g.:
|
||||
# [{"model":"utm.source","fields":["name"]},{"model":"utm.medium","fields":["name"]}]
|
||||
# Duplicate (model, fields) entries are collapsed. Prints "[]" when none.
|
||||
#
|
||||
# Prerequisites: ORIGIN_VERSION and FINAL_VERSION must be exported.
|
||||
collect_new_unique_constraints() {
|
||||
local versions_path="${PROJECT_ROOT}/versions"
|
||||
local v
|
||||
local -a json_parts=()
|
||||
local -A seen=()
|
||||
|
||||
for v in $(seq $((ORIGIN_VERSION + 1)) "$FINAL_VERSION"); do
|
||||
local yaml_file="${versions_path}/${v}.0/known_changes.yaml"
|
||||
[[ -f "$yaml_file" ]] || continue
|
||||
|
||||
local count
|
||||
count=$(yq '.new_unique_constraints | length' "$yaml_file" 2>/dev/null)
|
||||
[[ "$count" =~ ^[0-9]+$ && "$count" -gt 0 ]] || continue
|
||||
|
||||
local i
|
||||
for ((i = 0; i < count; i++)); do
|
||||
local model fields entry
|
||||
model=$(yq -o=json ".new_unique_constraints[$i].model" "$yaml_file" 2>/dev/null)
|
||||
fields=$(yq -o=json -I=0 ".new_unique_constraints[$i].fields" "$yaml_file" 2>/dev/null)
|
||||
[[ -n "$model" && "$model" != "null" ]] || continue
|
||||
[[ -n "$fields" && "$fields" != "null" ]] || continue
|
||||
entry="{\"model\":${model},\"fields\":${fields}}"
|
||||
if [[ -z "${seen[$entry]:-}" ]]; then
|
||||
seen["$entry"]=1
|
||||
json_parts+=("$entry")
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
local IFS=','
|
||||
printf '[%s]' "${json_parts[*]}"
|
||||
}
|
||||
|
||||
export PROJECT_ROOT DATASTORE_PATH FILESTORE_SUBPATH
|
||||
export -f log_info log_warn log_error log_step confirm_or_exit
|
||||
export -f check_required_commands
|
||||
export -f query_postgres_container copy_database copy_filestore run_compose exec_python_script_in_odoo_shell
|
||||
export -f exec_python_script_in_odoo_shell_with_preamble
|
||||
export -f classify_missing_addons
|
||||
export -f collect_new_unique_constraints
|
||||
|
||||
@@ -1,304 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Pre-Migration Unique Constraint Deduplication Script for Odoo
|
||||
|
||||
Some Odoo versions introduce new UNIQUE constraints on fields that previously
|
||||
allowed duplicates (e.g. utm.source.name in 16.0). When the migration rebuilds
|
||||
the model, the unique index creation raises
|
||||
`psycopg2.errors.UniqueViolation: could not create unique index ...`
|
||||
and aborts the whole registry load.
|
||||
|
||||
This script runs BEFORE the migration loop, on the SOURCE database, and renames
|
||||
duplicate records so the target-version unique index can be built successfully.
|
||||
|
||||
For each declared constraint (model + fields), it:
|
||||
- resolves the table, column type and translatable flag from the ORM;
|
||||
- detects duplicate groups (for translatable/jsonb fields the comparison is
|
||||
done on the value actually indexed by Odoo, i.e. the `en_US` key);
|
||||
- keeps the record with the smallest id untouched and renames every other
|
||||
duplicate by appending ` [<id>]` to ALL language keys (jsonb) or to the
|
||||
plain value (varchar), preserving all foreign keys.
|
||||
|
||||
Constraints to check are passed as JSON via the DEDUP_UNIQUE_CONSTRAINTS
|
||||
environment variable, e.g.:
|
||||
[{"model": "utm.source", "fields": ["name"]}, ...]
|
||||
|
||||
A machine-readable report of every rename is written to
|
||||
/tmp/dedup_unique_constraints_<db>_<timestamp>.json when DEDUP_REPORT is set.
|
||||
|
||||
Usage:
|
||||
compose run <service> shell -d <db> --no-http --stop-after-init \
|
||||
< dedup_unique_constraints.py
|
||||
|
||||
Exit codes:
|
||||
0 - Completed (duplicates, if any, were deduplicated)
|
||||
2 - Technical failure (see traceback)
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
from datetime import datetime
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
class Colors:
|
||||
RED = '\033[91m'
|
||||
GREEN = '\033[92m'
|
||||
YELLOW = '\033[93m'
|
||||
BLUE = '\033[94m'
|
||||
BOLD = '\033[1m'
|
||||
END = '\033[0m'
|
||||
|
||||
|
||||
def _c(color, text):
|
||||
return f"{color}{text}{Colors.END}"
|
||||
|
||||
|
||||
def load_constraints():
|
||||
"""Load the constraints catalog from the environment."""
|
||||
raw = os.environ.get('DEDUP_UNIQUE_CONSTRAINTS', '').strip()
|
||||
if not raw:
|
||||
return []
|
||||
try:
|
||||
data = json.loads(raw)
|
||||
except json.JSONDecodeError as exc:
|
||||
raise ValueError(f"DEDUP_UNIQUE_CONSTRAINTS is not valid JSON: {exc}")
|
||||
if not isinstance(data, list):
|
||||
raise ValueError("DEDUP_UNIQUE_CONSTRAINTS must be a JSON list")
|
||||
return data
|
||||
|
||||
|
||||
def resolve_constraint(env, spec):
|
||||
"""Resolve a {model, fields} spec into concrete DB metadata.
|
||||
|
||||
Returns a dict with table, list of (field_name, column, is_jsonb) or
|
||||
None if the model/fields are not present in this database (e.g. module
|
||||
not installed in the source version).
|
||||
"""
|
||||
model_name = spec.get('model')
|
||||
field_names = spec.get('fields') or []
|
||||
if not model_name or not field_names:
|
||||
return None
|
||||
if model_name not in env:
|
||||
return None
|
||||
|
||||
model = env[model_name]
|
||||
table = model._table
|
||||
|
||||
# The physical table must exist in the source DB.
|
||||
env.cr.execute("SELECT to_regclass(%s)", (table,))
|
||||
if not env.cr.fetchone()[0]:
|
||||
return None
|
||||
|
||||
columns = []
|
||||
for fname in field_names:
|
||||
field = model._fields.get(fname)
|
||||
if field is None:
|
||||
return None
|
||||
column = field.name
|
||||
# Column must physically exist (skip non-stored/computed fields).
|
||||
# We rely on the ACTUAL PostgreSQL column type, not the ORM `translate`
|
||||
# attribute: in the source version a translatable field may still be a
|
||||
# plain `character varying` column (it only becomes `jsonb` during the
|
||||
# migration to the version that introduces translation storage).
|
||||
env.cr.execute(
|
||||
"""
|
||||
SELECT data_type FROM information_schema.columns
|
||||
WHERE table_name = %s AND column_name = %s
|
||||
""",
|
||||
(table, column),
|
||||
)
|
||||
row = env.cr.fetchone()
|
||||
if not row:
|
||||
return None
|
||||
data_type = row[0]
|
||||
columns.append({
|
||||
'field': fname,
|
||||
'column': column,
|
||||
'is_jsonb': data_type == 'jsonb',
|
||||
})
|
||||
|
||||
return {'model': model_name, 'table': table, 'columns': columns}
|
||||
|
||||
|
||||
def key_sql(column, is_jsonb):
|
||||
"""SQL expression yielding the value Odoo compares for the unique index.
|
||||
|
||||
For jsonb (translated) columns Odoo indexes the value; the semantically
|
||||
relevant duplicate is on the `en_US` key. For plain columns, the value.
|
||||
"""
|
||||
if is_jsonb:
|
||||
return f"({column} ->> 'en_US')"
|
||||
return column
|
||||
|
||||
|
||||
def find_duplicate_groups(env, meta):
|
||||
"""Return duplicate groups as list of dicts: {value, ids (sorted)}."""
|
||||
table = meta['table']
|
||||
key_exprs = [key_sql(c['column'], c['is_jsonb']) for c in meta['columns']]
|
||||
key_concat = " || '\x1f' || ".join(f"COALESCE({e}, '')" for e in key_exprs)
|
||||
|
||||
query = f"""
|
||||
SELECT {key_concat} AS dup_key, array_agg(id ORDER BY id) AS ids
|
||||
FROM {table}
|
||||
GROUP BY {key_concat}
|
||||
HAVING COUNT(*) > 1
|
||||
"""
|
||||
env.cr.execute(query)
|
||||
groups = []
|
||||
for dup_key, ids in env.cr.fetchall():
|
||||
groups.append({'value': dup_key, 'ids': list(ids)})
|
||||
return groups
|
||||
|
||||
|
||||
def rename_record(env, meta, record_id):
|
||||
"""Suffix ` [<id>]` on every language key (jsonb) / plain value (varchar).
|
||||
|
||||
Returns a list of {field, old_value, new_value} describing the change.
|
||||
"""
|
||||
table = meta['table']
|
||||
changes = []
|
||||
suffix = f" [{record_id}]"
|
||||
|
||||
for col in meta['columns']:
|
||||
column = col['column']
|
||||
env.cr.execute(
|
||||
f"SELECT {column} FROM {table} WHERE id = %s", (record_id,)
|
||||
)
|
||||
row = env.cr.fetchone()
|
||||
if not row:
|
||||
continue
|
||||
old_value = row[0]
|
||||
|
||||
if col['is_jsonb']:
|
||||
# jsonb: append suffix to each language value
|
||||
if not isinstance(old_value, dict):
|
||||
# psycopg may already decode jsonb to dict; if not, load it.
|
||||
try:
|
||||
old_value = json.loads(old_value) if old_value else {}
|
||||
except (TypeError, ValueError):
|
||||
old_value = {}
|
||||
new_value = {
|
||||
lang: (val or '') + suffix for lang, val in old_value.items()
|
||||
}
|
||||
env.cr.execute(
|
||||
f"UPDATE {table} SET {column} = %s::jsonb WHERE id = %s",
|
||||
(json.dumps(new_value), record_id),
|
||||
)
|
||||
else:
|
||||
new_value = (old_value or '') + suffix
|
||||
env.cr.execute(
|
||||
f"UPDATE {table} SET {column} = %s WHERE id = %s",
|
||||
(new_value, record_id),
|
||||
)
|
||||
|
||||
changes.append({
|
||||
'field': col['field'],
|
||||
'old_value': old_value,
|
||||
'new_value': new_value,
|
||||
})
|
||||
|
||||
return changes
|
||||
|
||||
|
||||
def main():
|
||||
print("\n" + "=" * 80)
|
||||
print(_c(Colors.BOLD, "PRE-MIGRATION - UNIQUE CONSTRAINT DEDUPLICATION"))
|
||||
print("=" * 80 + "\n")
|
||||
|
||||
try:
|
||||
specs = load_constraints()
|
||||
except ValueError as exc:
|
||||
print(_c(Colors.RED, f"[ERROR] {exc}"))
|
||||
sys.exit(2)
|
||||
|
||||
if not specs:
|
||||
print(_c(Colors.YELLOW, "No unique constraints declared. Nothing to do."))
|
||||
return
|
||||
|
||||
renamed_records = [] # flat report entries
|
||||
checked = 0
|
||||
skipped = []
|
||||
|
||||
for spec in specs:
|
||||
meta = resolve_constraint(env, spec)
|
||||
label = f"{spec.get('model')}({', '.join(spec.get('fields', []))})"
|
||||
if meta is None:
|
||||
skipped.append(label)
|
||||
print(_c(Colors.YELLOW, f"[SKIP] {label} - not present in this database"))
|
||||
continue
|
||||
|
||||
checked += 1
|
||||
groups = find_duplicate_groups(env, meta)
|
||||
if not groups:
|
||||
print(_c(Colors.GREEN, f"[OK] {label} - no duplicates"))
|
||||
continue
|
||||
|
||||
total_dups = sum(len(g['ids']) - 1 for g in groups)
|
||||
print(_c(Colors.BOLD,
|
||||
f"[FIX] {label} - {len(groups)} duplicate group(s), "
|
||||
f"{total_dups} record(s) to rename"))
|
||||
|
||||
for group in groups:
|
||||
ids = group['ids']
|
||||
keep_id, dup_ids = ids[0], ids[1:]
|
||||
print(f" value={group['value']!r} keep id={keep_id} "
|
||||
f"rename ids={dup_ids}")
|
||||
for dup_id in dup_ids:
|
||||
changes = rename_record(env, meta, dup_id)
|
||||
for ch in changes:
|
||||
renamed_records.append({
|
||||
'model': meta['model'],
|
||||
'table': meta['table'],
|
||||
'id': dup_id,
|
||||
'field': ch['field'],
|
||||
'old_value': ch['old_value'],
|
||||
'new_value': ch['new_value'],
|
||||
})
|
||||
|
||||
if renamed_records:
|
||||
env.cr.commit()
|
||||
|
||||
# ---- Summary ---------------------------------------------------------
|
||||
print("\n" + "=" * 80)
|
||||
print(_c(Colors.BOLD, "DEDUPLICATION SUMMARY"))
|
||||
print("=" * 80)
|
||||
print(f" Constraints checked ... {checked}")
|
||||
print(f" Constraints skipped ... {len(skipped)}")
|
||||
print(f" Records renamed ....... {len(renamed_records)}")
|
||||
if renamed_records:
|
||||
print("")
|
||||
for r in renamed_records:
|
||||
old_display = r['old_value']
|
||||
new_display = r['new_value']
|
||||
print(f" - {r['model']} #{r['id']} [{r['field']}]: "
|
||||
f"{old_display!r} -> {new_display!r}")
|
||||
print("=" * 80 + "\n")
|
||||
|
||||
# ---- JSON report -----------------------------------------------------
|
||||
if os.environ.get('DEDUP_REPORT') and renamed_records:
|
||||
ts = datetime.now().strftime('%Y%m%d_%H%M%S')
|
||||
db = env.cr.dbname
|
||||
path = f"/tmp/dedup_unique_constraints_{db}_{ts}.json"
|
||||
report = {
|
||||
'type': 'dedup_unique_constraints',
|
||||
'timestamp': datetime.now().isoformat(),
|
||||
'database': db,
|
||||
'constraints_checked': checked,
|
||||
'constraints_skipped': skipped,
|
||||
'renamed_count': len(renamed_records),
|
||||
'renamed': renamed_records,
|
||||
}
|
||||
with open(path, 'w') as fh:
|
||||
json.dump(report, fh, indent=2, default=str)
|
||||
print(_c(Colors.BLUE, f"[REPORT] JSON written to {path}"))
|
||||
|
||||
|
||||
try:
|
||||
main()
|
||||
except Exception as exc: # noqa: BLE001
|
||||
print(_c(Colors.RED, f"[ERROR] Deduplication script failed: {exc}"))
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(2)
|
||||
@@ -1 +0,0 @@
|
||||
env['ir.attachment']._gc_file_store_unsafe()
|
||||
@@ -23,66 +23,6 @@ EOF
|
||||
)
|
||||
query_postgres_container "$CLEANUP_SQL" "$DB_NAME"
|
||||
|
||||
# ────────────────────────────────────────────────────────────
|
||||
# Purge orphaned ``ir.ui.view`` images whose parent view was
|
||||
# deleted AND whose ID is not referenced in any other view's
|
||||
# HTML (``arch_db``). An image with ``res_id = 0`` can still
|
||||
# be used by a live page via ``/web/image/<id>``, so we only
|
||||
# remove it when no ``arch_db`` contains that URL.
|
||||
# ────────────────────────────────────────────────────────────
|
||||
echo "Purging ir.ui.view images orphaned of their parent view and unreferenced in any arch_db..."
|
||||
PURGE_VIEW_IMAGES_SQL=$(cat <<'EOF'
|
||||
DO $$
|
||||
DECLARE purged int;
|
||||
BEGIN
|
||||
WITH refs AS (
|
||||
SELECT DISTINCT (m[1])::bigint AS ref_id
|
||||
FROM ir_ui_view v
|
||||
CROSS JOIN LATERAL regexp_matches(
|
||||
COALESCE(v.arch_db::text, ''), '/web/(?:image|content)/(\d+)', 'g') AS m
|
||||
)
|
||||
DELETE FROM ir_attachment a
|
||||
WHERE a.res_model = 'ir.ui.view'
|
||||
AND a.mimetype LIKE 'image/%'
|
||||
AND a.store_fname IS NOT NULL
|
||||
AND a.res_id NOT IN (SELECT id FROM ir_ui_view)
|
||||
AND NOT EXISTS (SELECT 1 FROM refs r WHERE r.ref_id = a.id);
|
||||
GET DIAGNOSTICS purged = ROW_COUNT;
|
||||
RAISE NOTICE 'Purged % orphaned ir.ui.view image attachments', purged;
|
||||
END $$;
|
||||
EOF
|
||||
)
|
||||
query_postgres_container "$PURGE_VIEW_IMAGES_SQL" "$DB_NAME"
|
||||
|
||||
# ────────────────────────────────────────────────────────────
|
||||
# Purge ir_logging (Python logger messages, no business value).
|
||||
# ────────────────────────────────────────────────────────────
|
||||
echo "Purging ir_logging..."
|
||||
query_postgres_container "TRUNCATE ir_logging;" "$DB_NAME"
|
||||
|
||||
# ────────────────────────────────────────────────────────────
|
||||
# Purge resized partner images (128, 256, 512, 1024) and
|
||||
# attachments with no res_model (orphaned factur-x, CSS, ICS).
|
||||
# Odoo 13+ regenerates thumbnails automatically from
|
||||
# image_1920 on the next partner write, so only the full-size
|
||||
# source needs to be kept.
|
||||
# ────────────────────────────────────────────────────────────
|
||||
echo "Purging resized partner images and orphaned attachments..."
|
||||
PURGE_ATTACHMENTS_SQL=$(cat <<'EOF'
|
||||
DELETE FROM ir_attachment
|
||||
WHERE res_model = 'res.partner'
|
||||
AND (name ILIKE '%image_128%'
|
||||
OR name ILIKE '%image_256%'
|
||||
OR name ILIKE '%image_512%'
|
||||
OR name ILIKE '%image_1024%');
|
||||
|
||||
DELETE FROM ir_attachment
|
||||
WHERE (res_model IS NULL OR res_model = '')
|
||||
AND store_fname IS NOT NULL;
|
||||
EOF
|
||||
)
|
||||
query_postgres_container "$PURGE_ATTACHMENTS_SQL" "$DB_NAME"
|
||||
|
||||
# ────────────────────────────────────────────────────────────
|
||||
# Fix orphan ir_filters pointing to deleted ir_actions
|
||||
#
|
||||
@@ -153,15 +93,7 @@ SET state = 'installed'
|
||||
WHERE state = 'to upgrade';
|
||||
" "$DB_NAME" || true
|
||||
|
||||
# ────────────────────────────────────────────────────────────
|
||||
# Clean orphaned filestore files left behind by the attachment
|
||||
# deletions above. ``_gc_file_store_unsafe`` scans the filestore
|
||||
# and removes files that no longer have a matching
|
||||
# ``ir_attachment`` record.
|
||||
# ────────────────────────────────────────────────────────────
|
||||
echo "Cleaning orphaned filestore files..."
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
exec_python_script_in_odoo_shell "$ODOO_SERVICE" "$DB_NAME" "$SCRIPT_DIR/lib/python/file_gc.py"
|
||||
|
||||
PYTHON_SCRIPT="${SCRIPT_DIR}/lib/python/fix_duplicated_views.py"
|
||||
echo "Remove duplicated views with script $PYTHON_SCRIPT ..."
|
||||
|
||||
@@ -87,30 +87,6 @@ else
|
||||
fi
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
#############################################
|
||||
## Deduplicate soon-to-be-unique fields ##
|
||||
#############################################
|
||||
# Some target versions add UNIQUE constraints on fields that allowed duplicates
|
||||
# in the source version (e.g. utm.source.name in 16.0). Left untouched, the
|
||||
# unique index creation aborts the migration with a UniqueViolation. We rename
|
||||
# duplicates in the source DB now, before the migration loop.
|
||||
log_step "UNIQUE CONSTRAINTS DEDUP"
|
||||
UNIQUE_CONSTRAINTS_JSON=$(collect_new_unique_constraints)
|
||||
if [[ "$UNIQUE_CONSTRAINTS_JSON" == "[]" || -z "$UNIQUE_CONSTRAINTS_JSON" ]]; then
|
||||
log_info "No new unique constraints declared for the traversed versions — skipping."
|
||||
else
|
||||
log_info "Checking unique constraints: $UNIQUE_CONSTRAINTS_JSON"
|
||||
DEDUP_SCRIPT="${SCRIPT_DIR}/lib/python/dedup_unique_constraints.py"
|
||||
# Inject the catalog via a Python preamble because the compose wrapper does
|
||||
# not reliably propagate host environment variables into the container.
|
||||
exec_python_script_in_odoo_shell_with_preamble "$DB_NAME" "$DB_NAME" "$DEDUP_SCRIPT" <<PY
|
||||
import os
|
||||
os.environ['DEDUP_UNIQUE_CONSTRAINTS'] = ${UNIQUE_CONSTRAINTS_JSON@Q}
|
||||
os.environ['DEDUP_REPORT'] = '1'
|
||||
PY
|
||||
fi
|
||||
|
||||
PYTHON_SCRIPT="${SCRIPT_DIR}/lib/python/check_views.py"
|
||||
echo "Check views with script $PYTHON_SCRIPT ..."
|
||||
exec_python_script_in_odoo_shell "$DB_NAME" "$DB_NAME" "$PYTHON_SCRIPT"
|
||||
|
||||
16
upgrade.sh
16
upgrade.sh
@@ -232,20 +232,4 @@ log_step "POST-UPGRADE PROCESSES"
|
||||
"${SCRIPT_DIR}/scripts/finalize_db.sh" "$FINALE_DB_NAME" "$FINALE_SERVICE_NAME"
|
||||
|
||||
log_step "UPGRADE PROCESS ENDED WITH SUCCESS"
|
||||
|
||||
# Report any records that were renamed to satisfy new unique constraints.
|
||||
# The deduplication runs on the source DB (ou${ORIGIN_VERSION}) during
|
||||
# prepare_db.sh and writes a timestamped JSON report to /tmp.
|
||||
readarray -t dedup_reports < <(ls -1t /tmp/dedup_unique_constraints_"${COPY_DB_NAME}"_*.json 2>/dev/null || true)
|
||||
if [[ ${#dedup_reports[@]} -gt 0 ]]; then
|
||||
latest_report="${dedup_reports[0]}"
|
||||
renamed_count=$(yq -p=json '.renamed_count' "$latest_report" 2>/dev/null || echo "?")
|
||||
log_info "Unique-constraint deduplication: ${renamed_count} record(s) renamed in the source DB before migration."
|
||||
log_info "Details: ${latest_report}"
|
||||
if [[ "$renamed_count" =~ ^[0-9]+$ && "$renamed_count" -gt 0 ]]; then
|
||||
yq -p=json -r '.renamed[] | " - \(.model) #\(.id) [\(.field)]: \(.old_value) -> \(.new_value)"' \
|
||||
"$latest_report" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
log_info "Full logs available at: ${LOG_FILE}"
|
||||
|
||||
@@ -21,12 +21,3 @@ renamed:
|
||||
new: server_action_mass_edit
|
||||
- old: crm_project
|
||||
new: crm_lead_to_task
|
||||
|
||||
# Unique constraints newly introduced in 16.0
|
||||
new_unique_constraints:
|
||||
- model: utm.source
|
||||
fields: [name]
|
||||
- model: utm.medium
|
||||
fields: [name]
|
||||
- model: utm.campaign
|
||||
fields: [name]
|
||||
|
||||
@@ -51,166 +51,6 @@ EOF
|
||||
echo "SQL command = $PRE_MIGRATE_SQL_3"
|
||||
query_postgres_container "$PRE_MIGRATE_SQL_3" ou17 || exit 1
|
||||
|
||||
# ────────────────────────────────────────────────────────────
|
||||
# Remove project_list's ir.actions.act_window.view records
|
||||
#
|
||||
# `project_list` (OCA) is not ported to 17.0 and is classified as
|
||||
# merged_in_core: in 17.0 the core `project` module natively adds the
|
||||
# kanban/tree views to the "Projects" actions (act_window_id 149 & 760).
|
||||
# 17.0 also introduces the unique constraint
|
||||
# `act_window_view_unique_mode_per_action = unique(act_window_id, view_mode)`.
|
||||
#
|
||||
# When core reloads project_project_views.xml, it INSERTs its own
|
||||
# (act_window_id, view_mode='kanban') rows, which collide with the rows still
|
||||
# owned by project_list -> UniqueViolation, aborting the registry load:
|
||||
#
|
||||
# ERROR: duplicate key value violates unique constraint
|
||||
# "act_window_view_unique_mode_per_action"
|
||||
# DETAIL: Key (act_window_id, view_mode)=(760, kanban) already exists.
|
||||
#
|
||||
# We delete project_list's act_window_view rows (and their ir_model_data) so
|
||||
# core can recreate its canonical rows. Verified against the 17.0 OpenUpgrade
|
||||
# scripts: none reference project_list xmlids nor remap these records, so this
|
||||
# is safe. Scoped to ir.actions.act_window.view only. Idempotent.
|
||||
# ────────────────────────────────────────────────────────────
|
||||
PRE_MIGRATE_SQL_4=$(cat <<'EOF'
|
||||
DO $$
|
||||
DECLARE
|
||||
deleted_count INTEGER;
|
||||
BEGIN
|
||||
WITH targets AS (
|
||||
SELECT d.id AS imd_id, d.res_id AS awv_id
|
||||
FROM ir_model_data d
|
||||
WHERE d.module = 'project_list'
|
||||
AND d.model = 'ir.actions.act_window.view'
|
||||
),
|
||||
del_views AS (
|
||||
DELETE FROM ir_act_window_view
|
||||
WHERE id IN (SELECT awv_id FROM targets)
|
||||
RETURNING id
|
||||
),
|
||||
del_imd AS (
|
||||
DELETE FROM ir_model_data
|
||||
WHERE id IN (SELECT imd_id FROM targets)
|
||||
RETURNING id
|
||||
)
|
||||
SELECT count(*) INTO deleted_count FROM del_views;
|
||||
|
||||
RAISE NOTICE 'Removed % project_list act_window_view record(s) to avoid act_window_view_unique_mode_per_action collision', deleted_count;
|
||||
END $$;
|
||||
EOF
|
||||
)
|
||||
echo "SQL command = $PRE_MIGRATE_SQL_4"
|
||||
query_postgres_container "$PRE_MIGRATE_SQL_4" ou17 || exit 1
|
||||
|
||||
# ────────────────────────────────────────────────────────────
|
||||
# Remove orphan theme.ir.ui.view templates pointing to deleted ir.ui.view
|
||||
#
|
||||
# In Odoo 17, the asset bundle system was reworked: `web._assets_utils`,
|
||||
# `website.assets_editor`, and `website._assets_frontend_helpers` were
|
||||
# replaced by the new `ir.asset` model. The theme_common/theme_clean 17.0
|
||||
# modules no longer define those templates, but the migrated database still
|
||||
# holds the old `theme_ir_ui_view` records (kept by ir_model_data noupdate).
|
||||
#
|
||||
# Their `inherit_id` Reference field points to ir.ui.view ids that no longer
|
||||
# exist. When website's `_theme_load(website)` runs (ir_module_module.py:95)
|
||||
# during the registry load, `ThemeView._convert_to_base_model`
|
||||
# (theme_models.py:85) accesses `inherit.website_id` on the ghost record and
|
||||
# raises MissingError, aborting the whole registry:
|
||||
#
|
||||
# odoo.exceptions.MissingError: Record does not exist or has been deleted.
|
||||
# (Record: ir.ui.view(174,), User: 1)
|
||||
#
|
||||
# This deletes the ir_model_data + theme_ir_ui_view rows whose inherit_id
|
||||
# targets a missing ir.ui.view. Generic (covers any theme, idempotent).
|
||||
# Scoped to theme_ir_ui_view only. Verified: no ir.ui.view copies exist for
|
||||
# the affected templates, so no side effects.
|
||||
# ────────────────────────────────────────────────────────────
|
||||
PRE_MIGRATE_SQL_5=$(cat <<'EOF'
|
||||
DO $$
|
||||
DECLARE
|
||||
deleted_imd INTEGER;
|
||||
deleted_tpl INTEGER;
|
||||
BEGIN
|
||||
IF to_regclass('theme_ir_ui_view') IS NULL THEN
|
||||
RAISE NOTICE 'Table theme_ir_ui_view does not exist, skipping stale view cleanup.';
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
WITH targets AS (
|
||||
SELECT id FROM theme_ir_ui_view
|
||||
WHERE inherit_id LIKE 'ir.ui.view,%'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM ir_ui_view v
|
||||
WHERE v.id = split_part(theme_ir_ui_view.inherit_id, ',', 2)::int
|
||||
)
|
||||
),
|
||||
del AS (
|
||||
DELETE FROM ir_model_data
|
||||
WHERE model = 'theme.ir.ui.view'
|
||||
AND res_id IN (SELECT id FROM targets)
|
||||
RETURNING id
|
||||
)
|
||||
SELECT count(*) INTO deleted_imd FROM del;
|
||||
|
||||
WITH del AS (
|
||||
DELETE FROM theme_ir_ui_view
|
||||
WHERE inherit_id LIKE 'ir.ui.view,%'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM ir_ui_view v
|
||||
WHERE v.id = split_part(theme_ir_ui_view.inherit_id, ',', 2)::int
|
||||
)
|
||||
RETURNING id
|
||||
)
|
||||
SELECT count(*) INTO deleted_tpl FROM del;
|
||||
|
||||
RAISE NOTICE 'Cleaned % stale theme_ir_ui_view record(s) and % ir_model_data entry(ies)', deleted_tpl, deleted_imd;
|
||||
END $$;
|
||||
EOF
|
||||
)
|
||||
echo "SQL command = $PRE_MIGRATE_SQL_5"
|
||||
query_postgres_container "$PRE_MIGRATE_SQL_5" ou17 || exit 1
|
||||
|
||||
# ────────────────────────────────────────────────────────────
|
||||
# Drop mail.tracking.value rows referencing event.registration.mobile
|
||||
#
|
||||
# In Odoo 17 the field event.registration.mobile is removed (OpenUpgrade:
|
||||
# "event / event.registration / mobile (char) : DEL"). During _process_end,
|
||||
# Odoo unlinks the now-orphan ir.model.fields row. mail's ir_model_fields
|
||||
# unlink() override iterates the mail.tracking.value rows pointing to it and
|
||||
# calls event.registration._mail_track_get_field_sequence('mobile'), which
|
||||
# does self._fields['mobile'] -> KeyError: 'mobile', aborting the registry:
|
||||
#
|
||||
# KeyError: 'mobile' (mail/models/models.py, _mail_track_get_field_sequence)
|
||||
#
|
||||
# The OpenUpgrade event script deletes the field but leaves its tracking
|
||||
# values, so any DB that tracked this field crashes. We delete those tracking
|
||||
# values beforehand so the field unlinks cleanly.
|
||||
# Scoped strictly to event.registration.mobile. Idempotent.
|
||||
# NOTE: this runs before -u all, so the FK column is still named 'field'
|
||||
# (mail renames it to 'field_id' during the 16->17 upgrade).
|
||||
# ────────────────────────────────────────────────────────────
|
||||
PRE_MIGRATE_SQL_6=$(cat <<'EOF'
|
||||
DO $$
|
||||
DECLARE
|
||||
deleted_count INTEGER;
|
||||
BEGIN
|
||||
WITH del AS (
|
||||
DELETE FROM mail_tracking_value
|
||||
WHERE field IN (
|
||||
SELECT id FROM ir_model_fields
|
||||
WHERE model = 'event.registration' AND name = 'mobile'
|
||||
)
|
||||
RETURNING id
|
||||
)
|
||||
SELECT count(*) INTO deleted_count FROM del;
|
||||
RAISE NOTICE 'Removed % mail_tracking_value row(s) for event.registration.mobile', deleted_count;
|
||||
END $$;
|
||||
EOF
|
||||
)
|
||||
echo "SQL command = $PRE_MIGRATE_SQL_6"
|
||||
query_postgres_container "$PRE_MIGRATE_SQL_6" ou17 || exit 1
|
||||
|
||||
# Copy filestores
|
||||
copy_filestore ou16 ou16 ou17 ou17 || exit 1
|
||||
|
||||
|
||||
@@ -6,56 +6,6 @@ echo "Prepare migration to 18.0..."
|
||||
# Copy database
|
||||
copy_database ou17 ou18 ou18 || exit 1
|
||||
|
||||
# ============================================================================
|
||||
# REMOVE contract_payment_mode (depends on obsolete account_payment_partner)
|
||||
#
|
||||
# contract_payment_mode v18 still depends on account_payment_partner (deleted
|
||||
# by the bank-payment renaming below) and uses the obsolete account.payment.mode
|
||||
# model. No v18 replacement exists — account_payment_base_oca provides no
|
||||
# contract integration. Only 2 contracts had payment_mode_id set, and the
|
||||
# migration to the new payment.method.line model is not supported.
|
||||
#
|
||||
# MUST run BEFORE the bank-payment renaming and BEFORE OpenUpgrade's
|
||||
# button_upgrade() which would otherwise crash on the missing dependency.
|
||||
#
|
||||
# We DELETE the module entirely (like the bank-payment merged_modules pattern)
|
||||
# rather than marking it 'to remove', because Odoo's "Transient module states
|
||||
# were reset" in load_modules() converts 'to remove' -> 'installed', causing
|
||||
# button_upgrade() to re-parse the missing account_payment_partner dependency
|
||||
# and abort the registry load. Deleting the row + ir_model_data + dependencies
|
||||
# ensures OpenUpgrade's update_list() rediscovers the physical addon as a fresh
|
||||
# 'uninstalled' module that button_upgrade() never touches.
|
||||
# ============================================================================
|
||||
contract_payment_mode_sql=$(cat <<'EOF'
|
||||
DO $$
|
||||
DECLARE
|
||||
mod_id INTEGER;
|
||||
BEGIN
|
||||
SELECT id INTO mod_id FROM ir_module_module WHERE name = 'contract_payment_mode';
|
||||
IF mod_id IS NOT NULL THEN
|
||||
DELETE FROM ir_module_module_dependency WHERE module_id = mod_id;
|
||||
DELETE FROM ir_model_data WHERE module = 'contract_payment_mode';
|
||||
DELETE FROM ir_module_module WHERE id = mod_id;
|
||||
|
||||
-- Rename the module's own external ID (base.module_contract_payment_mode)
|
||||
-- so update_list() can rediscover the physical contract_payment_mode addon
|
||||
-- (which still exists in v18 addons path) without conflicting on
|
||||
-- (base, module_contract_payment_mode). The orphaned xmlid pointed to
|
||||
-- the deleted ir.module.module row and has no other references.
|
||||
UPDATE ir_model_data
|
||||
SET name = 'module_contract_payment_mode_removed'
|
||||
WHERE module = 'base' AND name = 'module_contract_payment_mode';
|
||||
|
||||
RAISE NOTICE 'Removed contract_payment_mode module (depends on obsolete account_payment_partner)';
|
||||
ELSE
|
||||
RAISE NOTICE 'contract_payment_mode not found, skipping';
|
||||
END IF;
|
||||
END $$;
|
||||
EOF
|
||||
)
|
||||
echo "Removing contract_payment_mode module..."
|
||||
query_postgres_container "$contract_payment_mode_sql" ou18 || exit 1
|
||||
|
||||
# ============================================================================
|
||||
# BANK-PAYMENT -> BANK-PAYMENT-ALTERNATIVE MODULE RENAMING
|
||||
# Migration from OCA/bank-payment to OCA/bank-payment-alternative
|
||||
@@ -146,82 +96,6 @@ else
|
||||
echo "Module account_payment_mode not installed, skipping bank-payment migration."
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# ACCOUNT_FACTORING -> ACCOUNT_FACTORING_OCA MODULE RENAMING
|
||||
#
|
||||
# account_factoring (depends on account_payment_partner + account_payment_mode)
|
||||
# is replaced by account_factoring_oca (depends on account_payment_base_oca
|
||||
# + account_payment_base_oca_sale) in v18.
|
||||
#
|
||||
# Both modules have rigorously identical stored fields:
|
||||
# - account_journal: is_factor, factor_fee, factor_holdback_percent,
|
||||
# factor_partner_id, factor_fee_account_id, factor_holdback_account_id,
|
||||
# factor_limit_holdback_account_id, factor_tax_id, factor_validation
|
||||
# - account_move: factor_transfer_id, factor_payment_id,
|
||||
# payment_state_with_factor
|
||||
# - res_partner: factor_credit_limit
|
||||
#
|
||||
# Renaming (not uninstalling) preserves the 4910 invoice factor statuses,
|
||||
# 311 partner credit limits, and journal factor config. OpenUpgrade will
|
||||
# treat account_factoring_oca as already installed and adopt the existing
|
||||
# columns via ORM _auto_init.
|
||||
#
|
||||
# MUST run AFTER the bank-payment renaming block above, which creates
|
||||
# account_payment_base_oca in the DB.
|
||||
# ============================================================================
|
||||
ACCOUNT_FACTORING_RENAME_SQL=$(cat <<'EOF'
|
||||
DO $$
|
||||
DECLARE
|
||||
factor_module_id INTEGER;
|
||||
BEGIN
|
||||
SELECT id INTO factor_module_id FROM ir_module_module WHERE name = 'account_factoring';
|
||||
IF factor_module_id IS NOT NULL THEN
|
||||
-- Rename the module itself
|
||||
UPDATE ir_module_module SET name = 'account_factoring_oca' WHERE name = 'account_factoring';
|
||||
UPDATE ir_model_data SET module = 'account_factoring_oca' WHERE module = 'account_factoring';
|
||||
|
||||
-- Rename the module's own external ID (base.module_account_factoring)
|
||||
-- so OpenUpgrade can rediscover the physical account_factoring addon dir
|
||||
-- (which still exists in v18 addons path) without conflicting on
|
||||
-- (base, module_account_factoring) — that xmlid now points to the
|
||||
-- renamed module under its new name.
|
||||
UPDATE ir_model_data
|
||||
SET name = 'module_account_factoring_oca'
|
||||
WHERE module = 'base' AND name = 'module_account_factoring';
|
||||
|
||||
-- Redirect dependencies pointing TO the old name
|
||||
UPDATE ir_module_module_dependency SET name = 'account_factoring_oca' WHERE name = 'account_factoring';
|
||||
|
||||
-- Replace obsolete dependencies (account_payment_partner + account_payment_mode
|
||||
-- no longer exist in v18 addons) with the new OCA equivalents
|
||||
DELETE FROM ir_module_module_dependency
|
||||
WHERE module_id = factor_module_id
|
||||
AND name IN ('account_payment_partner', 'account_payment_mode');
|
||||
|
||||
INSERT INTO ir_module_module_dependency (name, module_id)
|
||||
SELECT 'account_payment_base_oca', factor_module_id
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM ir_module_module_dependency
|
||||
WHERE module_id = factor_module_id AND name = 'account_payment_base_oca'
|
||||
);
|
||||
|
||||
INSERT INTO ir_module_module_dependency (name, module_id)
|
||||
SELECT 'account_payment_base_oca_sale', factor_module_id
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM ir_module_module_dependency
|
||||
WHERE module_id = factor_module_id AND name = 'account_payment_base_oca_sale'
|
||||
);
|
||||
|
||||
RAISE NOTICE 'Renamed account_factoring -> account_factoring_oca (data preserved)';
|
||||
ELSE
|
||||
RAISE NOTICE 'account_factoring not found, skipping rename';
|
||||
END IF;
|
||||
END $$;
|
||||
EOF
|
||||
)
|
||||
echo "Executing account_factoring -> account_factoring_oca renaming..."
|
||||
query_postgres_container "$ACCOUNT_FACTORING_RENAME_SQL" ou18 || exit 1
|
||||
|
||||
# ============================================================================
|
||||
# FIX: Rename company-dependent columns before OpenUpgrade runs
|
||||
# In Odoo 18, company-dependent fields are stored as JSONB columns.
|
||||
@@ -247,6 +121,9 @@ EOF
|
||||
echo "Fixing company-dependent columns for Odoo 18..."
|
||||
query_postgres_container "$COMPANY_DEPENDENT_FIX_SQL" ou18 || exit 1
|
||||
|
||||
EOF
|
||||
)
|
||||
|
||||
# Execute SQL pre-migration commands
|
||||
PRE_MIGRATE_SQL=$(cat <<'EOF'
|
||||
UPDATE account_analytic_plan SET default_applicability=NULL WHERE default_applicability='optional';
|
||||
@@ -257,13 +134,6 @@ EOF
|
||||
echo "SQL command = $PRE_MIGRATE_SQL"
|
||||
query_postgres_container "$PRE_MIGRATE_SQL" ou18 || exit 1
|
||||
|
||||
# NOTE: Missing generic PCG account xml-ids expected by l10n_fr_account (e.g.
|
||||
# account.<company>_pcg_607_account / _pcg_707_account) are recreated generically
|
||||
# by the ORM post-migration script
|
||||
# versions/18.0/scripts/l10n_fr_account/18.0.2.2/post-migration-0k-fix-property-account-xmlids.py
|
||||
# which runs at stage "post" of l10n_fr_account, before the native "end" script
|
||||
# end-migrate_update_taxes.py that would otherwise crash on the missing xml-id.
|
||||
|
||||
# Copy filestores
|
||||
copy_filestore ou17 ou17 ou18 ou18 || exit 1
|
||||
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<!--
|
||||
0k / Élabore customization of the French VAT report (l10n_fr_account).
|
||||
|
||||
Adds an editable "adjustment" expression on box 25 (VAT credit) and
|
||||
integrates it into the box 25 balance formula. Historically this lived in a
|
||||
full copy of l10n_fr_account/data/tax_report_data.xml that shadowed the core
|
||||
module; it is now applied the OpenUpgrade way, via load_data() from
|
||||
post-migration.py, without duplicating the core module.
|
||||
|
||||
Loaded with mode="init"; records use the module's own xml ids
|
||||
(l10n_fr_account.*). tax_report_25_formula already exists in core and is
|
||||
updated here; tax_report_25_adjustment is new and gets created.
|
||||
-->
|
||||
<odoo>
|
||||
<!-- Update the box 25 balance formula to include the adjustment -->
|
||||
<record id="tax_report_25_formula" model="account.report.expression">
|
||||
<field name="report_line_id" ref="l10n_fr_account.tax_report_25"/>
|
||||
<field name="label">balance</field>
|
||||
<field name="engine">aggregation</field>
|
||||
<field name="formula">box_23.balance - box_16.balance + box_25.adjustment</field>
|
||||
<field name="subformula">if_above(EUR(0))</field>
|
||||
</record>
|
||||
|
||||
<!-- New editable external "adjustment" expression on box 25 -->
|
||||
<record id="tax_report_25_adjustment" model="account.report.expression">
|
||||
<field name="report_line_id" ref="l10n_fr_account.tax_report_25"/>
|
||||
<field name="label">adjustment</field>
|
||||
<field name="engine">external</field>
|
||||
<field name="formula">sum</field>
|
||||
<field name="subformula">editable;rounding=0</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -1,195 +0,0 @@
|
||||
# Copyright 0k / Élabore
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
#
|
||||
# Custom OpenUpgrade migration script, injected through a dedicated
|
||||
# --upgrade-path (see versions/18.0/upgrade.sh). Runs at stage "post" of
|
||||
# l10n_fr_account/18.0.2.2, i.e. BEFORE the global "end" stage where the native
|
||||
# l10n_fr_account/migrations/2.1/end-migrate_update_taxes.py runs.
|
||||
#
|
||||
# Problem
|
||||
# -------
|
||||
# end-migrate_update_taxes.py calls account.chart.template.try_loading('fr', ...).
|
||||
# During _post_load_data(), Odoo 18 resolves the French template's default
|
||||
# accounts via self.ref() WITHOUT raise_if_not_found=False, at three places:
|
||||
# - property_account_income_categ_id (pcg_707_account) -> chart_template.py:711
|
||||
# - property_account_expense_categ_id (pcg_607_account) -> chart_template.py:714
|
||||
# - every _get_property_accounts()/additional_properties account -> :746
|
||||
# If any expected account xml-id "account.<company>_<key>" is missing, it raises:
|
||||
# ValueError: External ID not found in the system: account.1_pcg_607_account
|
||||
# which aborts the whole registry load.
|
||||
#
|
||||
# This happens when a database customized its chart of accounts: the generic PCG
|
||||
# account was deleted (e.g. 607000) or recreated without its xml-id (e.g. 707000).
|
||||
#
|
||||
# Fix (generic, not hardcoded to 607/707)
|
||||
# ---------------------------------------
|
||||
# For every company with a chart_template, we read the list of account xml-ids
|
||||
# the template expects (dynamically, from the template ORM API), and for each one
|
||||
# that is missing in the DB we recreate the "account.<company>_<key>" ir.model.data
|
||||
# pointing to the best matching real account:
|
||||
# 1. the account whose code matches exactly the template account code,
|
||||
# 2. otherwise the company account whose code shares the longest common prefix
|
||||
# with the template code (e.g. 607000 -> 607100),
|
||||
# 3. otherwise nothing (logged as a warning; never blocks).
|
||||
# No account is modified; only missing xml-ids are (re)created. Idempotent.
|
||||
import logging
|
||||
|
||||
from openupgradelib import openupgrade
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _collect_property_account_xmlids(template, code, accounts_csv):
|
||||
"""Return the set of template account xml-ids referenced as property accounts.
|
||||
|
||||
Sources:
|
||||
- template._get_property_accounts({}) (receivable, payable, expense, income, ...)
|
||||
- the template_data dict (property_account_*_id keys)
|
||||
- additional_properties declared on res.company template data
|
||||
Only keep values that are real account xml-ids of this template, i.e. present
|
||||
in accounts_csv (this filters out journals and dotted external ids).
|
||||
"""
|
||||
xmlids = set()
|
||||
|
||||
# 1. Base property accounts (model-level defaults)
|
||||
try:
|
||||
prop_fields = template._get_property_accounts({})
|
||||
except Exception as exc: # noqa: BLE001
|
||||
_logger.warning("Could not read _get_property_accounts: %s", exc)
|
||||
prop_fields = {}
|
||||
|
||||
# 2. Full template data: template_data holds property_account_*_id values,
|
||||
# res.company holds additional_properties (currency exchange, deferral...).
|
||||
try:
|
||||
tdata = template._get_chart_template_data(code)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
_logger.warning("Could not read _get_chart_template_data(%s): %s", code, exc)
|
||||
tdata = {}
|
||||
|
||||
candidate_values = []
|
||||
|
||||
template_data = tdata.get("template_data", {}) if tdata else {}
|
||||
for key, value in template_data.items():
|
||||
if key in prop_fields or key.startswith("property_"):
|
||||
candidate_values.append(value)
|
||||
|
||||
# additional_properties live inside the res.company template values
|
||||
res_company = tdata.get("res.company", {}) if tdata else {}
|
||||
for _company_key, company_vals in res_company.items():
|
||||
if not isinstance(company_vals, dict):
|
||||
continue
|
||||
add_props = company_vals.get("additional_properties")
|
||||
if isinstance(add_props, dict):
|
||||
candidate_values.extend(add_props.values())
|
||||
|
||||
# Keep only plain (non-dotted) xml-ids that are actual accounts of the template
|
||||
for value in candidate_values:
|
||||
if isinstance(value, str) and value and "." not in value and value in accounts_csv:
|
||||
xmlids.add(value)
|
||||
|
||||
return xmlids
|
||||
|
||||
|
||||
def _find_best_account(env, company, target_code):
|
||||
"""Return the best account record for a template account code, or None.
|
||||
|
||||
1. exact code match for the company;
|
||||
2. otherwise the company account whose code shares the longest common prefix
|
||||
with target_code; ties are broken by smallest code (deterministic).
|
||||
3. otherwise None.
|
||||
|
||||
The exact fallback account matters little functionally: it is only used as a
|
||||
journal's default_account_id / an ir.default, which stays user-overridable.
|
||||
The goal is to provide a valid account of the right family so that
|
||||
try_loading() no longer crashes on a missing external id.
|
||||
"""
|
||||
if not target_code:
|
||||
return None
|
||||
|
||||
Account = env["account.account"]
|
||||
company_domain = [("company_ids", "in", company.id)]
|
||||
|
||||
exact = Account.search(company_domain + [("code", "=", target_code)], limit=1)
|
||||
if exact:
|
||||
return exact
|
||||
|
||||
# Longest common prefix fallback; ties broken by smallest code.
|
||||
candidates = Account.search(company_domain)
|
||||
best = None
|
||||
best_key = None # (prefix_len, negated-code-for-min) -> maximize
|
||||
for acc in candidates:
|
||||
code = acc.code or ""
|
||||
common = 0
|
||||
for a, b in zip(code, target_code):
|
||||
if a != b:
|
||||
break
|
||||
common += 1
|
||||
if common == 0:
|
||||
continue
|
||||
key = (common, tuple(-ord(c) for c in code)) # longer prefix, then smallest code
|
||||
if best_key is None or key > best_key:
|
||||
best = acc
|
||||
best_key = key
|
||||
return best
|
||||
|
||||
|
||||
@openupgrade.migrate()
|
||||
def migrate(env, version):
|
||||
template = env["account.chart.template"]
|
||||
companies = env["res.company"].search([("chart_template", "!=", False)])
|
||||
|
||||
created = 0
|
||||
for company in companies:
|
||||
code = company.chart_template
|
||||
tmpl = template.with_company(company)
|
||||
|
||||
try:
|
||||
accounts_csv = tmpl._get_account_account(code)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
_logger.warning(
|
||||
"Company %s (%s): cannot read template accounts: %s",
|
||||
company.id, code, exc,
|
||||
)
|
||||
continue
|
||||
|
||||
expected = _collect_property_account_xmlids(tmpl, code, accounts_csv)
|
||||
|
||||
for tmpl_xmlid in sorted(expected):
|
||||
full = f"account.{company.id}_{tmpl_xmlid}"
|
||||
if env.ref(full, raise_if_not_found=False):
|
||||
continue
|
||||
if company.parent_ids:
|
||||
parent_full = (
|
||||
f"account.{company.parent_ids[0].id}_{tmpl_xmlid}"
|
||||
)
|
||||
if env.ref(parent_full, raise_if_not_found=False):
|
||||
continue
|
||||
|
||||
target_code = accounts_csv.get(tmpl_xmlid, {}).get("code")
|
||||
account = _find_best_account(env, company, target_code)
|
||||
if not account:
|
||||
_logger.warning(
|
||||
"Company %s: no account found for template xml-id %s "
|
||||
"(code %s); skipping",
|
||||
company.id, tmpl_xmlid, target_code,
|
||||
)
|
||||
continue
|
||||
|
||||
env["ir.model.data"].create({
|
||||
"module": "account",
|
||||
"name": f"{company.id}_{tmpl_xmlid}",
|
||||
"model": "account.account",
|
||||
"res_id": account.id,
|
||||
"noupdate": True,
|
||||
})
|
||||
created += 1
|
||||
_logger.info(
|
||||
"Company %s: created xml-id %s -> account %s (%s) for "
|
||||
"template code %s",
|
||||
company.id, full, account.id, account.code, target_code,
|
||||
)
|
||||
|
||||
_logger.info(
|
||||
"[l10n_fr_account] property-account xml-id fix: %s xml-id(s) created",
|
||||
created,
|
||||
)
|
||||
@@ -1,20 +0,0 @@
|
||||
# Copyright 0k / Élabore
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
#
|
||||
# Custom OpenUpgrade migration script, injected through a dedicated
|
||||
# --upgrade-path (see versions/18.0/upgrade.sh). It runs IN ADDITION to the
|
||||
# native openupgrade_scripts post-migration.py of l10n_fr_account/18.0.2.2.
|
||||
#
|
||||
# Loads the 0k customization of the French VAT report (box 25 "adjustment")
|
||||
# the OpenUpgrade way, without shadowing the core l10n_fr_account module.
|
||||
from openupgradelib import openupgrade
|
||||
|
||||
|
||||
@openupgrade.migrate()
|
||||
def migrate(env, version):
|
||||
# mode="init" (re)creates records marked noupdate that the normal upgrade
|
||||
# mechanism would skip; it updates existing ones (tax_report_25_formula)
|
||||
# and creates new ones (tax_report_25_adjustment).
|
||||
openupgrade.load_data(
|
||||
env, "l10n_fr_account", "18.0.2.2/noupdate_changes.xml"
|
||||
)
|
||||
@@ -1,460 +0,0 @@
|
||||
# Copyright 0k / Élabore
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
#
|
||||
# Custom OpenUpgrade migration script, injected through a dedicated
|
||||
# --upgrade-path (see versions/18.0/upgrade.sh). It runs IN ADDITION to the
|
||||
# native openupgrade_scripts pre-migration.py of l10n_fr_account/18.0.2.2:
|
||||
# the framework appends every upgrade path, so this script does not override
|
||||
# nor duplicate the native one.
|
||||
from openupgradelib import openupgrade
|
||||
|
||||
|
||||
@openupgrade.migrate()
|
||||
def migrate(env, version):
|
||||
"""Fix orphan account.report.expression records before XML data loading.
|
||||
|
||||
During migration from V17, some account.report.expression records lose
|
||||
their ir_model_data entries. When l10n_fr_account loads tax_report_data.xml
|
||||
in V18, it tries to INSERT these records again, violating the unique
|
||||
constraint account_report_expression_line_label_uniq (report_line_id, label).
|
||||
|
||||
Fix 1: Re-insert missing ir_model_data entries so Odoo performs UPDATE
|
||||
instead of INSERT when loading the XML data.
|
||||
|
||||
Fix 2: Delete orphan expressions that no longer exist in V18 XML
|
||||
(box_X5, box_Y4, box_Y5, box_Z5 now use aggregation_formula field).
|
||||
"""
|
||||
cr = env.cr
|
||||
# (expr_xmlid, line_xmlid, label)
|
||||
# Generated from l10n_fr_account/data/tax_report_data.xml (18.0)
|
||||
expressions = [
|
||||
("tax_report_A1_tag", "tax_report_A1", "balance"),
|
||||
("tax_report_A1_tag_rounded", "tax_report_A1", "balance_rounded"),
|
||||
("tax_report_A1_balance_from_tags", "tax_report_A1", "balance_from_tags"),
|
||||
("tax_report_A1_adjustment", "tax_report_A1", "adjustment"),
|
||||
("tax_report_A2_tag", "tax_report_A2", "balance"),
|
||||
("tax_report_A2_tag_rounded", "tax_report_A2", "balance_rounded"),
|
||||
("tax_report_A2_balance_from_tags", "tax_report_A2", "balance_from_tags"),
|
||||
("tax_report_A2_adjustment", "tax_report_A2", "adjustment"),
|
||||
("tax_report_A3_tag", "tax_report_A3", "balance"),
|
||||
("tax_report_A3_tag_rounded", "tax_report_A3", "balance_rounded"),
|
||||
("tax_report_A3_balance_from_tags", "tax_report_A3", "balance_from_tags"),
|
||||
("tax_report_A3_adjustment", "tax_report_A3", "adjustment"),
|
||||
("tax_report_A4_tag", "tax_report_A4", "balance"),
|
||||
("tax_report_A4_tag_rounded", "tax_report_A4", "balance_rounded"),
|
||||
("tax_report_A4_balance_from_tags", "tax_report_A4", "balance_from_tags"),
|
||||
("tax_report_A4_adjustment", "tax_report_A4", "adjustment"),
|
||||
("tax_report_A5_tag", "tax_report_A5", "balance"),
|
||||
("tax_report_A5_tag_rounded", "tax_report_A5", "balance_rounded"),
|
||||
("tax_report_A5_balance_from_tags", "tax_report_A5", "balance_from_tags"),
|
||||
("tax_report_A5_adjustment", "tax_report_A5", "adjustment"),
|
||||
("tax_report_B1_tag", "tax_report_B1", "balance"),
|
||||
("tax_report_B1_tag_rounded", "tax_report_B1", "balance_rounded"),
|
||||
("tax_report_B1_balance_from_tags", "tax_report_B1", "balance_from_tags"),
|
||||
("tax_report_B1_adjustment", "tax_report_B1", "adjustment"),
|
||||
("tax_report_B2_tag", "tax_report_B2", "balance"),
|
||||
("tax_report_B2_tag_rounded", "tax_report_B2", "balance_rounded"),
|
||||
("tax_report_B2_balance_from_tags", "tax_report_B2", "balance_from_tags"),
|
||||
("tax_report_B2_adjustment", "tax_report_B2", "adjustment"),
|
||||
("tax_report_B3_tag", "tax_report_B3", "balance"),
|
||||
("tax_report_B3_tag_rounded", "tax_report_B3", "balance_rounded"),
|
||||
("tax_report_B3_balance_from_tags", "tax_report_B3", "balance_from_tags"),
|
||||
("tax_report_B3_adjustment", "tax_report_B3", "adjustment"),
|
||||
("tax_report_B4_tag", "tax_report_B4", "balance"),
|
||||
("tax_report_B4_tag_rounded", "tax_report_B4", "balance_rounded"),
|
||||
("tax_report_B4_balance_from_tags", "tax_report_B4", "balance_from_tags"),
|
||||
("tax_report_B4_adjustment", "tax_report_B4", "adjustment"),
|
||||
("tax_report_B5_tag", "tax_report_B5", "balance"),
|
||||
("tax_report_B5_tag_rounded", "tax_report_B5", "balance_rounded"),
|
||||
("tax_report_B5_balance_from_tags", "tax_report_B5", "balance_from_tags"),
|
||||
("tax_report_B5_adjustment", "tax_report_B5", "adjustment"),
|
||||
("tax_report_E1_tag", "tax_report_E1", "balance"),
|
||||
("tax_report_E1_tag_rounded", "tax_report_E1", "balance_rounded"),
|
||||
("tax_report_E1_balance_from_tags", "tax_report_E1", "balance_from_tags"),
|
||||
("tax_report_E1_adjustment", "tax_report_E1", "adjustment"),
|
||||
("tax_report_E2_tag", "tax_report_E2", "balance"),
|
||||
("tax_report_E2_tag_rounded", "tax_report_E2", "balance_rounded"),
|
||||
("tax_report_E2_balance_from_tags", "tax_report_E2", "balance_from_tags"),
|
||||
("tax_report_E2_adjustment", "tax_report_E2", "adjustment"),
|
||||
("tax_report_E3_tag", "tax_report_E3", "balance"),
|
||||
("tax_report_E3_tag_rounded", "tax_report_E3", "balance_rounded"),
|
||||
("tax_report_E3_balance_from_tags", "tax_report_E3", "balance_from_tags"),
|
||||
("tax_report_E3_adjustment", "tax_report_E3", "adjustment"),
|
||||
("tax_report_E4_tag", "tax_report_E4", "balance"),
|
||||
("tax_report_E4_tag_rounded", "tax_report_E4", "balance_rounded"),
|
||||
("tax_report_E4_balance_from_tags", "tax_report_E4", "balance_from_tags"),
|
||||
("tax_report_E4_adjustment", "tax_report_E4", "adjustment"),
|
||||
("tax_report_E5_tag", "tax_report_E5", "balance"),
|
||||
("tax_report_E5_tag_rounded", "tax_report_E5", "balance_rounded"),
|
||||
("tax_report_E5_balance_from_tags", "tax_report_E5", "balance_from_tags"),
|
||||
("tax_report_E5_adjustment", "tax_report_E5", "adjustment"),
|
||||
("tax_report_E6_tag", "tax_report_E6", "balance"),
|
||||
("tax_report_E6_tag_rounded", "tax_report_E6", "balance_rounded"),
|
||||
("tax_report_E6_balance_from_tags", "tax_report_E6", "balance_from_tags"),
|
||||
("tax_report_E6_adjustment", "tax_report_E6", "adjustment"),
|
||||
("tax_report_F1_tag", "tax_report_F1", "balance"),
|
||||
("tax_report_F1_tag_rounded", "tax_report_F1", "balance_rounded"),
|
||||
("tax_report_F1_balance_from_tags", "tax_report_F1", "balance_from_tags"),
|
||||
("tax_report_F1_adjustment", "tax_report_F1", "adjustment"),
|
||||
("tax_report_F2_tag", "tax_report_F2", "balance"),
|
||||
("tax_report_F2_tag_rounded", "tax_report_F2", "balance_rounded"),
|
||||
("tax_report_F2_balance_from_tags", "tax_report_F2", "balance_from_tags"),
|
||||
("tax_report_F2_adjustment", "tax_report_F2", "adjustment"),
|
||||
("tax_report_F3_tag", "tax_report_F3", "balance"),
|
||||
("tax_report_F3_tag_rounded", "tax_report_F3", "balance_rounded"),
|
||||
("tax_report_F3_balance_from_tags", "tax_report_F3", "balance_from_tags"),
|
||||
("tax_report_F3_adjustment", "tax_report_F3", "adjustment"),
|
||||
("tax_report_F4_tag", "tax_report_F4", "balance"),
|
||||
("tax_report_F4_tag_rounded", "tax_report_F4", "balance_rounded"),
|
||||
("tax_report_F4_balance_from_tags", "tax_report_F4", "balance_from_tags"),
|
||||
("tax_report_F4_adjustment", "tax_report_F4", "adjustment"),
|
||||
("tax_report_F5_tag", "tax_report_F5", "balance"),
|
||||
("tax_report_F5_tag_rounded", "tax_report_F5", "balance_rounded"),
|
||||
("tax_report_F5_balance_from_tags", "tax_report_F5", "balance_from_tags"),
|
||||
("tax_report_F5_adjustment", "tax_report_F5", "adjustment"),
|
||||
("tax_report_F6_tag", "tax_report_F6", "balance"),
|
||||
("tax_report_F6_tag_rounded", "tax_report_F6", "balance_rounded"),
|
||||
("tax_report_F6_balance_from_tags", "tax_report_F6", "balance_from_tags"),
|
||||
("tax_report_F6_adjustment", "tax_report_F6", "adjustment"),
|
||||
("tax_report_F7_tag", "tax_report_F7", "balance"),
|
||||
("tax_report_F7_tag_rounded", "tax_report_F7", "balance_rounded"),
|
||||
("tax_report_F7_balance_from_tags", "tax_report_F7", "balance_from_tags"),
|
||||
("tax_report_F7_adjustment", "tax_report_F7", "adjustment"),
|
||||
("tax_report_F8_tag", "tax_report_F8", "balance"),
|
||||
("tax_report_F8_tag_rounded", "tax_report_F8", "balance_rounded"),
|
||||
("tax_report_F8_balance_from_tags", "tax_report_F8", "balance_from_tags"),
|
||||
("tax_report_F8_adjustment", "tax_report_F8", "adjustment"),
|
||||
("tax_report_F9_tag", "tax_report_F9", "balance"),
|
||||
("tax_report_F9_tag_rounded", "tax_report_F9", "balance_rounded"),
|
||||
("tax_report_F9_balance_from_tags", "tax_report_F9", "balance_from_tags"),
|
||||
("tax_report_F9_adjustment", "tax_report_F9", "adjustment"),
|
||||
("tax_report_08_base_tag", "tax_report_08_base", "balance"),
|
||||
("tax_report_08_base_tag_rounded", "tax_report_08_base", "balance_rounded"),
|
||||
("tax_report_08_base_balance_from_tags", "tax_report_08_base", "balance_from_tags"),
|
||||
("tax_report_08_base_adjustment", "tax_report_08_base", "adjustment"),
|
||||
("tax_report_08_taxe_tag", "tax_report_08_taxe", "balance"),
|
||||
("tax_report_08_taxe_tag_rounded", "tax_report_08_taxe", "balance_rounded"),
|
||||
("tax_report_08_taxe_tag_no_rounding", "tax_report_08_taxe", "balance_from_tags"),
|
||||
("tax_report_09_base_tag", "tax_report_09_base", "balance"),
|
||||
("tax_report_09_base_tag_rounded", "tax_report_09_base", "balance_rounded"),
|
||||
("tax_report_09_base_balance_from_tags", "tax_report_09_base", "balance_from_tags"),
|
||||
("tax_report_09_base_adjustment", "tax_report_09_base", "adjustment"),
|
||||
("tax_report_09_taxe_tag", "tax_report_09_taxe", "balance"),
|
||||
("tax_report_09_taxe_tag_rounded", "tax_report_09_taxe", "balance_rounded"),
|
||||
("tax_report_09_taxe_tag_no_rounding", "tax_report_09_taxe", "balance_from_tags"),
|
||||
("tax_report_9B_base_tag", "tax_report_9B_base", "balance"),
|
||||
("tax_report_9B_base_tag_rounded", "tax_report_9B_base", "balance_rounded"),
|
||||
("tax_report_9B_base_balance_from_tags", "tax_report_9B_base", "balance_from_tags"),
|
||||
("tax_report_9B_base_adjustment", "tax_report_9B_base", "adjustment"),
|
||||
("tax_report_9B_taxe_tag", "tax_report_9B_taxe", "balance"),
|
||||
("tax_report_9B_taxe_tag_rounded", "tax_report_9B_taxe", "balance_rounded"),
|
||||
("tax_report_9B_taxe_tag_no_rounding", "tax_report_9B_taxe", "balance_from_tags"),
|
||||
("tax_report_10_base_tag", "tax_report_10_base", "balance"),
|
||||
("tax_report_10_base_tag_rounded", "tax_report_10_base", "balance_rounded"),
|
||||
("tax_report_10_base_balance_from_tags", "tax_report_10_base", "balance_from_tags"),
|
||||
("tax_report_10_base_adjustment", "tax_report_10_base", "adjustment"),
|
||||
("tax_report_10_taxe_tag", "tax_report_10_taxe", "balance"),
|
||||
("tax_report_10_taxe_tag_balance", "tax_report_10_taxe", "balance_rounded"),
|
||||
("tax_report_10_taxe_tag_no_rounding", "tax_report_10_taxe", "balance_from_tags"),
|
||||
("tax_report_11_base_tag", "tax_report_11_base", "balance"),
|
||||
("tax_report_11_base_tag_rounded", "tax_report_11_base", "balance_rounded"),
|
||||
("tax_report_11_base_balance_from_tags", "tax_report_11_base", "balance_from_tags"),
|
||||
("tax_report_11_base_adjustment", "tax_report_11_base", "adjustment"),
|
||||
("tax_report_11_taxe_tag", "tax_report_11_taxe", "balance"),
|
||||
("tax_report_11_taxe_tag_rounded", "tax_report_11_taxe", "balance_rounded"),
|
||||
("tax_report_11_taxe_tag_no_rounding", "tax_report_11_taxe", "balance_from_tags"),
|
||||
("tax_report_T1_base_tag", "tax_report_T1_base", "balance"),
|
||||
("tax_report_T1_base_tag_rounded", "tax_report_T1_base", "balance_rounded"),
|
||||
("tax_report_T1_base_balance_from_tags", "tax_report_T1_base", "balance_from_tags"),
|
||||
("tax_report_T1_base_adjustment", "tax_report_T1_base", "adjustment"),
|
||||
("tax_report_T1_taxe_tag", "tax_report_T1_taxe", "balance"),
|
||||
("tax_report_T1_taxe_tag_rounded", "tax_report_T1_taxe", "balance_rounded"),
|
||||
("tax_report_T1_taxe_tag_no_rounding", "tax_report_T1_taxe", "balance_from_tags"),
|
||||
("tax_report_T2_base_tag", "tax_report_T2_base", "balance"),
|
||||
("tax_report_T2_base_tag_rounded", "tax_report_T2_base", "balance_rounded"),
|
||||
("tax_report_T2_base_balance_from_tags", "tax_report_T2_base", "balance_from_tags"),
|
||||
("tax_report_T2_base_adjustment", "tax_report_T2_base", "adjustment"),
|
||||
("tax_report_T2_taxe_tag", "tax_report_T2_taxe", "balance"),
|
||||
("tax_report_T2_taxe_tag_rounded", "tax_report_T2_taxe", "balance_rounded"),
|
||||
("tax_report_T2_taxe_tag_no_rounding", "tax_report_T2_taxe", "balance_from_tags"),
|
||||
("tax_report_T3_base_tag", "tax_report_T3_base", "balance"),
|
||||
("tax_report_T3_base_tag_rounded", "tax_report_T3_base", "balance_rounded"),
|
||||
("tax_report_T3_base_balance_from_tags", "tax_report_T3_base", "balance_from_tags"),
|
||||
("tax_report_T3_base_adjustment", "tax_report_T3_base", "adjustment"),
|
||||
("tax_report_T3_taxe_tag", "tax_report_T3_taxe", "balance"),
|
||||
("tax_report_T3_taxe_tag_rounded", "tax_report_T3_taxe", "balance_rounded"),
|
||||
("tax_report_T3_taxe_tag_no_rounding", "tax_report_T3_taxe", "balance_from_tags"),
|
||||
("tax_report_T4_base_tag", "tax_report_T4_base", "balance"),
|
||||
("tax_report_T4_base_tag_rounded", "tax_report_T4_base", "balance_rounded"),
|
||||
("tax_report_T4_base_balance_from_tags", "tax_report_T4_base", "balance_from_tags"),
|
||||
("tax_report_T4_base_adjustment", "tax_report_T4_base", "adjustment"),
|
||||
("tax_report_T4_taxe_tag", "tax_report_T4_taxe", "balance"),
|
||||
("tax_report_T4_taxe_tag_balance", "tax_report_T4_taxe", "balance_rounded"),
|
||||
("tax_report_T4_taxe_tag_no_rounding", "tax_report_T4_taxe", "balance_from_tags"),
|
||||
("tax_report_T5_base_tag", "tax_report_T5_base", "balance"),
|
||||
("tax_report_T5_base_tag_rounded", "tax_report_T5_base", "balance_rounded"),
|
||||
("tax_report_T5_base_balance_from_tags", "tax_report_T5_base", "balance_from_tags"),
|
||||
("tax_report_T5_base_adjustment", "tax_report_T5_base", "adjustment"),
|
||||
("tax_report_T5_taxe_tag", "tax_report_T5_taxe", "balance"),
|
||||
("tax_report_T5_taxe_tag_rounded", "tax_report_T5_taxe", "balance_rounded"),
|
||||
("tax_report_T5_taxe_tag_no_rounding", "tax_report_T5_taxe", "balance_from_tags"),
|
||||
("tax_report_T6_base_tag", "tax_report_T6_base", "balance"),
|
||||
("tax_report_T6_base_tag_balance", "tax_report_T6_base", "balance_rounded"),
|
||||
("tax_report_T6_base_balance_from_tags", "tax_report_T6_base", "balance_from_tags"),
|
||||
("tax_report_T6_base_adjustment", "tax_report_T6_base", "adjustment"),
|
||||
("tax_report_T6_taxe_tag", "tax_report_T6_taxe", "balance"),
|
||||
("tax_report_T6_taxe_tag_balance", "tax_report_T6_taxe", "balance_rounded"),
|
||||
("tax_report_T6_taxe_tag_no_rounding", "tax_report_T6_taxe", "balance_from_tags"),
|
||||
("tax_report_T7_base_tag", "tax_report_T7_base", "balance"),
|
||||
("tax_report_T7_base_tag_rounded", "tax_report_T7_base", "balance_rounded"),
|
||||
("tax_report_T7_base_balance_from_tags", "tax_report_T7_base", "balance_from_tags"),
|
||||
("tax_report_T7_base_adjustment", "tax_report_T7_base", "adjustment"),
|
||||
("tax_report_T7_taxe_tag", "tax_report_T7_taxe", "balance"),
|
||||
("tax_report_T7_taxe_balance_rounded", "tax_report_T7_taxe", "balance_rounded"),
|
||||
("tax_report_T7_taxe_balance_from_tags", "tax_report_T7_taxe", "balance_from_tags"),
|
||||
("tax_report_13_base_tag", "tax_report_13_base", "balance"),
|
||||
("tax_report_13_base_tag_rounded", "tax_report_13_base", "balance_rounded"),
|
||||
("tax_report_13_base_balance_from_tags", "tax_report_13_base", "balance_from_tags"),
|
||||
("tax_report_13_base_adjustment", "tax_report_13_base", "adjustment"),
|
||||
("tax_report_13_taxe_tag", "tax_report_13_taxe", "balance"),
|
||||
("tax_report_13_taxe_balance_rounded", "tax_report_13_taxe", "balance_rounded"),
|
||||
("tax_report_13_taxe_balance_from_tags", "tax_report_13_taxe", "balance_from_tags"),
|
||||
("tax_report_14_base_tag", "tax_report_14_base", "balance"),
|
||||
("tax_report_14_base_tag_rounded", "tax_report_14_base", "balance_rounded"),
|
||||
("tax_report_14_base_balance_from_tags", "tax_report_14_base", "balance_from_tags"),
|
||||
("tax_report_14_base_adjustment", "tax_report_14_base", "adjustment"),
|
||||
("tax_report_14_taxe_tag", "tax_report_14_taxe", "balance"),
|
||||
("tax_report_14_taxe_balance_rounded", "tax_report_14_taxe", "balance_rounded"),
|
||||
("tax_report_14_taxe_balance_from_tags", "tax_report_14_taxe", "balance_from_tags"),
|
||||
("tax_report_P1_base_tag", "tax_report_P1_base", "balance"),
|
||||
("tax_report_P1_base_tag_rounded", "tax_report_P1_base", "balance_rounded"),
|
||||
("tax_report_P1_base_balance_from_tags", "tax_report_P1_base", "balance_from_tags"),
|
||||
("tax_report_P1_base_adjustment", "tax_report_P1_base", "adjustment"),
|
||||
("tax_report_P1_taxe_tag", "tax_report_P1_taxe", "balance"),
|
||||
("tax_report_P1_taxe_tag_rounded", "tax_report_P1_taxe", "balance_rounded"),
|
||||
("tax_report_P1_taxe_tag_no_rounding", "tax_report_P1_taxe", "balance_from_tags"),
|
||||
("tax_report_P2_base_tag", "tax_report_P2_base", "balance"),
|
||||
("tax_report_P2_base_tag_rounded", "tax_report_P2_base", "balance_rounded"),
|
||||
("tax_report_P2_base_balance_from_tags", "tax_report_P2_base", "balance_from_tags"),
|
||||
("tax_report_P2_base_adjustment", "tax_report_P2_base", "adjustment"),
|
||||
("tax_report_P2_taxe_tag", "tax_report_P2_taxe", "balance"),
|
||||
("tax_report_P2_taxe_tag_rounded", "tax_report_P2_taxe", "balance_rounded"),
|
||||
("tax_report_P2_taxe_tag_no_rounding", "tax_report_P2_taxe", "balance_from_tags"),
|
||||
("tax_report_I1_base_tag", "tax_report_I1_base", "balance"),
|
||||
("tax_report_I1_base_tag_rounded", "tax_report_I1_base", "balance_rounded"),
|
||||
("tax_report_I1_base_balance_from_tags", "tax_report_I1_base", "balance_from_tags"),
|
||||
("tax_report_I1_base_adjustment", "tax_report_I1_base", "adjustment"),
|
||||
("tax_report_I1_taxe_tag", "tax_report_I1_taxe", "balance"),
|
||||
("tax_report_I1_taxe_tag_rounded", "tax_report_I1_taxe", "balance_rounded"),
|
||||
("tax_report_I1_taxe_tag_no_rounding", "tax_report_I1_taxe", "balance_from_tags"),
|
||||
("tax_report_I2_base_tag", "tax_report_I2_base", "balance"),
|
||||
("tax_report_I2_base_tag_rounded", "tax_report_I2_base", "balance_rounded"),
|
||||
("tax_report_I2_base_balance_from_tags", "tax_report_I2_base", "balance_from_tags"),
|
||||
("tax_report_I2_base_adjustment", "tax_report_I2_base", "adjustment"),
|
||||
("tax_report_I2_taxe_tag", "tax_report_I2_taxe", "balance"),
|
||||
("tax_report_I2_taxe_tag_rounded", "tax_report_I2_taxe", "balance_rounded"),
|
||||
("tax_report_I2_taxe_tag_no_rounding", "tax_report_I2_taxe", "balance_from_tags"),
|
||||
("tax_report_I3_base_tag", "tax_report_I3_base", "balance"),
|
||||
("tax_report_I3_base_tag_rounded", "tax_report_I3_base", "balance_rounded"),
|
||||
("tax_report_I3_base_balance_from_tags", "tax_report_I3_base", "balance_from_tags"),
|
||||
("tax_report_I3_base_adjustment", "tax_report_I3_base", "adjustment"),
|
||||
("tax_report_I3_taxe_tag", "tax_report_I3_taxe", "balance"),
|
||||
("tax_report_I3_taxe_tag_rounded", "tax_report_I3_taxe", "balance_rounded"),
|
||||
("tax_report_I3_taxe_tag_no_rounding", "tax_report_I3_taxe", "balance_from_tags"),
|
||||
("tax_report_I4_base_tag", "tax_report_I4_base", "balance"),
|
||||
("tax_report_I4_base_tag_rounded", "tax_report_I4_base", "balance_rounded"),
|
||||
("tax_report_I4_base_balance_from_tags", "tax_report_I4_base", "balance_from_tags"),
|
||||
("tax_report_I4_base_adjustment", "tax_report_I4_base", "adjustment"),
|
||||
("tax_report_I4_taxe_tag", "tax_report_I4_taxe", "balance"),
|
||||
("tax_report_I4_taxe_tag_rounded", "tax_report_I4_taxe", "balance_rounded"),
|
||||
("tax_report_I4_taxe_tag_no_rounding", "tax_report_I4_taxe", "balance_from_tags"),
|
||||
("tax_report_I5_base_tag", "tax_report_I5_base", "balance"),
|
||||
("tax_report_I5_base_tag_rounded", "tax_report_I5_base", "balance_rounded"),
|
||||
("tax_report_I5_base_balance_from_tags", "tax_report_I5_base", "balance_from_tags"),
|
||||
("tax_report_I5_base_adjustment", "tax_report_I5_base", "adjustment"),
|
||||
("tax_report_I5_taxe_tag", "tax_report_I5_taxe", "balance"),
|
||||
("tax_report_I5_taxe_tag_rounded", "tax_report_I5_taxe", "balance_rounded"),
|
||||
("tax_report_I5_taxe_tag_no_rounding", "tax_report_I5_taxe", "balance_from_tags"),
|
||||
("tax_report_I6_base_tag", "tax_report_I6_base", "balance"),
|
||||
("tax_report_I6_base_tag_rounded", "tax_report_I6_base", "balance_rounded"),
|
||||
("tax_report_I6_base_balance_from_tags", "tax_report_I6_base", "balance_from_tags"),
|
||||
("tax_report_I6_base_adjustment", "tax_report_I6_base", "adjustment"),
|
||||
("tax_report_I6_taxe_tag", "tax_report_I6_taxe", "balance"),
|
||||
("tax_report_I6_taxe_tag_rounded", "tax_report_I6_taxe", "balance_rounded"),
|
||||
("tax_report_I6_taxe_tag_no_rounding", "tax_report_I6_taxe", "balance_from_tags"),
|
||||
("tax_report_15_tag", "tax_report_15", "balance"),
|
||||
("tax_report_15_balance_rounded", "tax_report_15", "balance_rounded"),
|
||||
("tax_report_15_balance_from_tags", "tax_report_15", "balance_from_tags"),
|
||||
("tax_report_15_1_tag", "tax_report_15_1", "balance"),
|
||||
("tax_report_15_1_balance_rounded", "tax_report_15_1", "balance_rounded"),
|
||||
("tax_report_15_1_balance_from_tags", "tax_report_15_1", "balance_from_tags"),
|
||||
("tax_report_15_2_tag", "tax_report_15_2", "balance"),
|
||||
("tax_report_15_2_balance_rounded", "tax_report_15_2", "balance_rounded"),
|
||||
("tax_report_15_2_balance_from_tags", "tax_report_15_2", "balance_from_tags"),
|
||||
("tax_report_5B_tag", "tax_report_5B", "balance"),
|
||||
("tax_report_5B_balance_rounded", "tax_report_5B", "balance_rounded"),
|
||||
("tax_report_5B_balance_from_tags", "tax_report_5B", "balance_from_tags"),
|
||||
("tax_report_16_formula", "tax_report_16", "balance"),
|
||||
("tax_report_17_tag", "tax_report_17", "balance"),
|
||||
("tax_report_17_balance_rounded", "tax_report_17", "balance_rounded"),
|
||||
("tax_report_17_balance_from_tags", "tax_report_17", "balance_from_tags"),
|
||||
("tax_report_18_tag", "tax_report_18", "balance"),
|
||||
("tax_report_18_balance_rounded", "tax_report_18", "balance_rounded"),
|
||||
("tax_report_18_balance_from_tags", "tax_report_18", "balance_from_tags"),
|
||||
("tax_report_19_tag", "tax_report_19", "balance"),
|
||||
("tax_report_19_balance_rounded", "tax_report_19", "balance_rounded"),
|
||||
("tax_report_19_balance_from_tags", "tax_report_19", "balance_from_tags"),
|
||||
("tax_report_20_tag", "tax_report_20", "balance"),
|
||||
("tax_report_20_balance_rounded", "tax_report_20", "balance_rounded"),
|
||||
("tax_report_20_balance_from_tags", "tax_report_20", "balance_from_tags"),
|
||||
("tax_report_21_tag", "tax_report_21", "balance"),
|
||||
("tax_report_21_balance_from_tags", "tax_report_21", "balance_from_tags"),
|
||||
("tax_report_22_applied_carryover", "tax_report_22", "_applied_carryover_balance"),
|
||||
("tax_report_22_tag", "tax_report_22", "tag"),
|
||||
("tax_report_22_balance_rounded", "tax_report_22", "balance_rounded"),
|
||||
("tax_report_22_balance", "tax_report_22", "balance"),
|
||||
("tax_report_2C_tag", "tax_report_2C", "balance"),
|
||||
("tax_report_2C_balance_rounded", "tax_report_2C", "balance_rounded"),
|
||||
("tax_report_2C_balance_from_tags", "tax_report_2C", "balance_from_tags"),
|
||||
("tax_report_22A_tag", "tax_report_22A", "balance"),
|
||||
("tax_report_22A_balance_rounded", "tax_report_22A", "balance_rounded"),
|
||||
("tax_report_22A_balance_from_tags", "tax_report_22A", "balance_from_tags"),
|
||||
("tax_report_23_formula", "tax_report_23", "balance"),
|
||||
("tax_report_24_tag", "tax_report_24", "balance"),
|
||||
("tax_report_24_balance_rounded", "tax_report_24", "balance_rounded"),
|
||||
("tax_report_24_balance_from_tags", "tax_report_24", "balance_from_tags"),
|
||||
("tax_report_2E_tag", "tax_report_2E", "balance"),
|
||||
("tax_report_2E_balance_rounded", "tax_report_2E", "balance_rounded"),
|
||||
("tax_report_2E_balance_from_tags", "tax_report_2E", "balance_from_tags"),
|
||||
("tax_report_25_formula", "tax_report_25", "balance"),
|
||||
("tax_report_25_adjustment", "tax_report_25", "adjustment"),
|
||||
("tax_report_td_formula", "tax_report_TD", "balance"),
|
||||
("tax_report_TICFE_tag", "tax_report_TICFE", "balance"),
|
||||
("tax_report_TICGN_tag", "tax_report_TICGN", "balance"),
|
||||
("tax_report_TICC_tag", "tax_report_TICC", "balance"),
|
||||
("tax_report_TIC_total_formula", "tax_report_TIC_total", "balance"),
|
||||
("tax_report_X1_tag", "tax_report_X1", "balance"),
|
||||
("tax_report_X1_balance_rounded", "tax_report_X1", "balance_rounded"),
|
||||
("tax_report_X1_balance_from_tags", "tax_report_X1", "balance_from_tags"),
|
||||
("tax_report_X2_tag", "tax_report_X2", "balance"),
|
||||
("tax_report_X2_balance_rounded", "tax_report_X2", "balance_rounded"),
|
||||
("tax_report_X2_balance_from_tags", "tax_report_X2", "balance_from_tags"),
|
||||
("tax_report_X3_tag", "tax_report_X3", "balance"),
|
||||
("tax_report_X3_balance_rounded", "tax_report_X3", "balance_rounded"),
|
||||
("tax_report_X3_balance_from_tags", "tax_report_X3", "balance_from_tags"),
|
||||
("tax_report_X4_formula", "tax_report_X4", "balance"),
|
||||
("tax_report_Y1_formula", "tax_report_Y1", "balance"),
|
||||
("tax_report_Y2_formula", "tax_report_Y2", "balance"),
|
||||
("tax_report_Y3_formula", "tax_report_Y3", "balance"),
|
||||
("tax_report_Z1_tag", "tax_report_Z1", "balance"),
|
||||
("tax_report_Z1_balance_rounded", "tax_report_Z1", "balance_rounded"),
|
||||
("tax_report_Z1_balance_from_tags", "tax_report_Z1", "balance_from_tags"),
|
||||
("tax_report_Z2_tag", "tax_report_Z2", "balance"),
|
||||
("tax_report_Z2_balance_rounded", "tax_report_Z2", "balance_rounded"),
|
||||
("tax_report_Z2_balance_from_tags", "tax_report_Z2", "balance_from_tags"),
|
||||
("tax_report_Z3_tag", "tax_report_Z3", "balance"),
|
||||
("tax_report_Z3_balance_rounded", "tax_report_Z3", "balance_rounded"),
|
||||
("tax_report_Z3_balance_from_tags", "tax_report_Z3", "balance_from_tags"),
|
||||
("tax_report_Z4_formula", "tax_report_Z4", "balance"),
|
||||
("tax_report_26_external_tag", "tax_report_26_external", "balance"),
|
||||
("tax_report_AA_tag", "tax_report_AA", "balance"),
|
||||
("tax_report_AA_balance_rounded", "tax_report_AA", "balance_rounded"),
|
||||
("tax_report_AA_balance_from_tags", "tax_report_AA", "balance_from_tags"),
|
||||
("tax_report_27_formula", "tax_report_27", "balance"),
|
||||
("tax_report_27_formula_temp", "tax_report_27", "_balance_temp"),
|
||||
("tax_report_27_carryover", "tax_report_27", "_carryover_balance"),
|
||||
("tax_report_28_formula", "tax_report_28", "balance"),
|
||||
("tax_report_29_tag", "tax_report_29", "balance"),
|
||||
("tax_report_29_balance_rounded", "tax_report_29", "balance_rounded"),
|
||||
("tax_report_29_balance_from_tags", "tax_report_29", "balance_from_tags"),
|
||||
("tax_report_32_formula", "tax_report_32", "balance"),
|
||||
]
|
||||
|
||||
# Fix 1: insert missing ir_model_data entries for orphan expressions
|
||||
cr.execute(
|
||||
"""
|
||||
SELECT imd.name, imd.res_id
|
||||
FROM ir_model_data imd
|
||||
WHERE imd.module = 'l10n_fr_account'
|
||||
AND imd.model = 'account.report.line'
|
||||
"""
|
||||
)
|
||||
line_xmlid_to_id = {row[0]: row[1] for row in cr.fetchall()}
|
||||
|
||||
cr.execute(
|
||||
"""
|
||||
SELECT res_id FROM ir_model_data
|
||||
WHERE model = 'account.report.expression'
|
||||
"""
|
||||
)
|
||||
known_expr_ids = {row[0] for row in cr.fetchall()}
|
||||
|
||||
inserted = 0
|
||||
for expr_xmlid, line_xmlid, label in expressions:
|
||||
line_id = line_xmlid_to_id.get(line_xmlid)
|
||||
if not line_id:
|
||||
continue
|
||||
|
||||
cr.execute(
|
||||
"""
|
||||
SELECT id FROM account_report_expression
|
||||
WHERE report_line_id = %s AND label = %s
|
||||
""",
|
||||
(line_id, label),
|
||||
)
|
||||
row = cr.fetchone()
|
||||
if not row:
|
||||
continue
|
||||
expr_id = row[0]
|
||||
|
||||
if expr_id in known_expr_ids:
|
||||
continue
|
||||
|
||||
cr.execute(
|
||||
"""
|
||||
INSERT INTO ir_model_data (name, module, model, res_id, noupdate, write_date, create_date)
|
||||
VALUES (%s, 'l10n_fr_account', 'account.report.expression', %s, false, NOW(), NOW())
|
||||
ON CONFLICT DO NOTHING
|
||||
""",
|
||||
(expr_xmlid, expr_id),
|
||||
)
|
||||
known_expr_ids.add(expr_id)
|
||||
inserted += 1
|
||||
|
||||
if inserted:
|
||||
print(
|
||||
f" [l10n_fr_account] Inserted {inserted} missing ir_model_data entries"
|
||||
" for orphan expressions"
|
||||
)
|
||||
|
||||
# Fix 2: delete orphan expressions obsolete in V18
|
||||
# box_X5, box_Y4, box_Y5, box_Z5 now use aggregation_formula field
|
||||
# instead of expression_ids, so their V17 expressions are obsolete.
|
||||
obsolete_line_xmlids = [
|
||||
"tax_report_X5",
|
||||
"tax_report_Y4",
|
||||
"tax_report_Y5",
|
||||
"tax_report_Z5",
|
||||
]
|
||||
cr.execute(
|
||||
"""
|
||||
DELETE FROM account_report_expression
|
||||
WHERE report_line_id IN (
|
||||
SELECT res_id FROM ir_model_data
|
||||
WHERE module = 'l10n_fr_account'
|
||||
AND model = 'account.report.line'
|
||||
AND name = ANY(%s)
|
||||
)
|
||||
AND id NOT IN (
|
||||
SELECT res_id FROM ir_model_data
|
||||
WHERE model = 'account.report.expression'
|
||||
)
|
||||
""",
|
||||
(obsolete_line_xmlids,),
|
||||
)
|
||||
deleted = cr.rowcount
|
||||
if deleted:
|
||||
print(
|
||||
f" [l10n_fr_account] Deleted {deleted} obsolete orphan expressions"
|
||||
" (X5/Y4/Y5/Z5)"
|
||||
)
|
||||
@@ -1,11 +1,4 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# --upgrade-path lists BOTH our custom migration scripts and OpenUpgrade's
|
||||
# native ones. Passing --upgrade-path overrides config['upgrade_path'], and
|
||||
# openupgrade_framework only sets that default when it is empty; so we must
|
||||
# include the native openupgrade_scripts/scripts path explicitly, otherwise the
|
||||
# native migration scripts would no longer run. Odoo appends every listed path
|
||||
# to odoo.upgrade.__path__ and concatenates (does not override) the scripts
|
||||
# found per module/version, so our script runs IN ADDITION to the native ones.
|
||||
run_compose run -p 8018:8069 ou18 --config=/opt/odoo/auto/odoo.conf --stop-after-init -u all --workers 0 --log-level=debug --max-cron-threads=0 --limit-time-real=10000 --database=ou18 --load=base,web,openupgrade_framework --addons-path=/opt/odoo/auto/mig,/opt/odoo/auto/addons --upgrade-path=/opt/odoo/auto/upgrade,/opt/odoo/auto/addons/openupgrade_scripts/scripts
|
||||
run_compose run -p 8018:8069 ou18 --config=/opt/odoo/auto/odoo.conf --stop-after-init -u all --workers 0 --log-level=debug --max-cron-threads=0 --limit-time-real=10000 --database=ou18 --load=base,web,openupgrade_framework
|
||||
|
||||
Reference in New Issue
Block a user