Compare commits
8 Commits
969414e649
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81e036cd4a | ||
|
|
aa48543a12 | ||
|
|
9cbb5eaff2 | ||
|
|
ef75d4a5b1 | ||
|
|
d2c4ec6de5 | ||
|
|
e27c309d06 | ||
|
|
8ea746a4d1 | ||
|
|
778730e396 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
||||
final_404_addons
|
||||
migration.log
|
||||
|
||||
74
README.md
74
README.md
@@ -9,6 +9,7 @@ A tool for migrating Odoo databases between major versions, using [OpenUpgrade](
|
||||
- [Project Structure](#project-structure)
|
||||
- [How It Works](#how-it-works)
|
||||
- [Usage](#usage)
|
||||
- [Resuming a Failed Migration](#resuming-a-failed-migration)
|
||||
- [Customization](#customization)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
|
||||
@@ -158,7 +159,7 @@ The script performs a **step-by-step migration** between each major version. For
|
||||
### Running the Migration
|
||||
|
||||
```bash
|
||||
./upgrade.sh <source_version> <target_version> <database_name> <source_service>
|
||||
./upgrade.sh <source_version> <target_version> <database_name> <source_service> [--resume-from|-r <version>]
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
@@ -169,6 +170,11 @@ The script performs a **step-by-step migration** between each major version. For
|
||||
| `database_name` | Database name | `my_prod_db` |
|
||||
| `source_service` | Source Docker Compose service | `odoo14` |
|
||||
|
||||
**Options:**
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `--resume-from <version>`, `-r <version>` | Resume from an intermediate checkpoint (see [Resuming a Failed Migration](#resuming-a-failed-migration)) |
|
||||
|
||||
**Example:**
|
||||
```bash
|
||||
./upgrade.sh 14 17 elabore_20241208 odoo14
|
||||
@@ -239,6 +245,48 @@ compose run odoo17 shell -d ou17 --no-http --stop-after-init < lib/python/valida
|
||||
- **JSON report** written to `/tmp/validation_views_<db>_<timestamp>.json`
|
||||
- **Exit code**: `0` = success, `1` = errors found
|
||||
|
||||
## Resuming a Failed Migration
|
||||
|
||||
Each version hop copies the database before modifying it (`ou14` → `ou15` → `ou16` → …). If a migration crashes mid-way, the intermediate databases from completed hops are still intact and can be used as a restart point.
|
||||
|
||||
### How It Works
|
||||
|
||||
Use `--resume-from <version>` (or `-r <version>`) to restart from an intermediate checkpoint:
|
||||
|
||||
```bash
|
||||
./upgrade.sh <source_version> <target_version> <database_name> <source_service> --resume-from <checkpoint_version>
|
||||
```
|
||||
|
||||
When a checkpoint is specified, the script:
|
||||
- **Skips** the initial database and filestore copy
|
||||
- **Skips** `prepare_db.sh` (already done before the crash)
|
||||
- **Starts the migration loop** from `checkpoint_version + 1`
|
||||
|
||||
### Example
|
||||
|
||||
A migration from 14 to 18 crashes during the 16→17 hop. The database `ou15` was successfully created. Resume from there:
|
||||
|
||||
```bash
|
||||
./upgrade.sh 14 18 my_database odoo14 --resume-from 15
|
||||
```
|
||||
|
||||
This runs: `16.0 → 17.0 → 18.0`, starting from `ou15`.
|
||||
|
||||
### Constraints
|
||||
|
||||
- The checkpoint version must be strictly between the source and target versions.
|
||||
- The intermediate database (`ou<version>`) and its filestore must exist before resuming.
|
||||
|
||||
### Restarting From Scratch
|
||||
|
||||
To restart a full migration from the beginning (ignoring all intermediate databases):
|
||||
|
||||
```bash
|
||||
./upgrade.sh 14 18 my_database odoo14
|
||||
```
|
||||
|
||||
The script automatically drops and recreates the final target database (`ou18`) if it already exists.
|
||||
|
||||
## Customization
|
||||
|
||||
### Version Scripts
|
||||
@@ -365,10 +413,30 @@ The script works on a **copy** of the original database. You can restart as many
|
||||
|
||||
### Viewing Detailed Logs
|
||||
|
||||
Odoo/OpenUpgrade logs are displayed in real-time. For a problematic migration:
|
||||
All output (scripts, Docker containers, OpenUpgrade logs) is automatically saved to `migration.log` at the project root. The file is overwritten at the start of each run (full migration or resume).
|
||||
|
||||
```bash
|
||||
# Follow live output while also saving to file (already done automatically)
|
||||
tail -f migration.log
|
||||
|
||||
# Search for errors after the fact
|
||||
grep -i error migration.log
|
||||
|
||||
# View with ANSI colors preserved (less handles CRLF line endings natively)
|
||||
less -R migration.log
|
||||
|
||||
# Plain text output without CRLF artifacts
|
||||
cat migration.log | col -b
|
||||
```
|
||||
|
||||
> **Note:** `migration.log` is recorded via a pseudo-TTY (`script`), which preserves
|
||||
> color output in the terminal. As a side effect, the file uses `\r\n` line endings.
|
||||
> Use `less -R` or `col -b` for clean viewing.
|
||||
|
||||
For a problematic migration:
|
||||
|
||||
1. Note the version where the error occurs
|
||||
2. Check the logs to identify the problematic module/table
|
||||
2. Check `migration.log` to identify the problematic module/table
|
||||
3. Add a fix in the `pre_upgrade.sh` for that version
|
||||
4. Restart the migration
|
||||
|
||||
|
||||
@@ -12,17 +12,6 @@ CLEANUP_SQL=$(cat <<'EOF'
|
||||
DROP SEQUENCE IF EXISTS base_registry_signaling;
|
||||
DROP SEQUENCE IF EXISTS base_cache_signaling;
|
||||
|
||||
-- Reset website templates to their original state.
|
||||
-- Views with arch_fs (file source) that have been customized (arch_db not null)
|
||||
-- are reset to use the file version, EXCEPT for actual website pages which
|
||||
-- contain user content that must be preserved.
|
||||
UPDATE ir_ui_view
|
||||
SET arch_db = NULL
|
||||
WHERE arch_fs IS NOT NULL
|
||||
AND arch_fs LIKE 'website/%'
|
||||
AND arch_db IS NOT NULL
|
||||
AND id NOT IN (SELECT view_id FROM website_page);
|
||||
|
||||
-- Purge compiled frontend assets (CSS/JS bundles).
|
||||
-- These cached files reference old asset versions and must be regenerated
|
||||
-- by Odoo after migration to avoid broken stylesheets and scripts.
|
||||
@@ -34,6 +23,24 @@ EOF
|
||||
)
|
||||
query_postgres_container "$CLEANUP_SQL" "$DB_NAME"
|
||||
|
||||
# Reset modules still marked as 'to upgrade' before launching the Odoo shell
|
||||
# scripts below. Loading the registry in `odoo shell` re-triggers the upgrade
|
||||
# of these modules, which can fail on broken views (e.g. website_sale
|
||||
# TypeError). The controlled `-u all` at the end of this script performs the
|
||||
# real update afterwards. We deliberately do NOT touch 'to install' modules:
|
||||
# forcing them to 'installed' would skip their install scripts entirely.
|
||||
#
|
||||
# Uncomment the SELECT below to trace which modules were pending upgrade
|
||||
# before we neutralize their state (useful if the `-u all` above is removed).
|
||||
# query_postgres_container "
|
||||
# SELECT name FROM ir_module_module WHERE state = 'to upgrade' ORDER BY name;
|
||||
# " "$DB_NAME" || true
|
||||
query_postgres_container "
|
||||
UPDATE ir_module_module
|
||||
SET state = 'installed'
|
||||
WHERE state = 'to upgrade';
|
||||
" "$DB_NAME" || true
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
PYTHON_SCRIPT="${SCRIPT_DIR}/lib/python/fix_duplicated_views.py"
|
||||
@@ -48,13 +55,18 @@ exec_python_script_in_odoo_shell "$DB_NAME" "$DB_NAME" "$PYTHON_SCRIPT"
|
||||
# Regenerate POS inalterability hashes if needed
|
||||
# ────────────────────────────────────────────────────────────
|
||||
HASHES_NEEDED=$(query_postgres_container "
|
||||
SELECT COUNT(*)
|
||||
FROM pos_order po
|
||||
JOIN res_company rc ON rc.id = po.company_id
|
||||
WHERE po.state IN ('paid', 'done', 'invoiced')
|
||||
AND rc.l10n_fr_pos_cert_sequence_id IS NOT NULL
|
||||
AND (po.l10n_fr_hash IS NULL OR po.l10n_fr_secure_sequence_number IS NULL)
|
||||
SELECT COUNT(*) FROM information_schema.tables WHERE table_name = 'pos_order'
|
||||
" "$DB_NAME")
|
||||
if [[ "$HASHES_NEEDED" =~ ^[0-9]+$ && "$HASHES_NEEDED" -gt 0 ]]; then
|
||||
HASHES_NEEDED=$(query_postgres_container "
|
||||
SELECT COUNT(*)
|
||||
FROM pos_order po
|
||||
JOIN res_company rc ON rc.id = po.company_id
|
||||
WHERE po.state IN ('paid', 'done', 'invoiced')
|
||||
AND rc.l10n_fr_pos_cert_sequence_id IS NOT NULL
|
||||
AND (po.l10n_fr_hash IS NULL OR po.l10n_fr_secure_sequence_number IS NULL)
|
||||
" "$DB_NAME")
|
||||
fi
|
||||
|
||||
if [[ "$HASHES_NEEDED" =~ ^[0-9]+$ && "$HASHES_NEEDED" -gt 0 ]]; then
|
||||
echo ""
|
||||
@@ -72,7 +84,7 @@ fi
|
||||
|
||||
|
||||
# Launch Odoo with database in finale version to run all updates
|
||||
run_compose --debug run "$ODOO_SERVICE" -u all --log-level=debug --stop-after-init --no-http
|
||||
run_compose --debug run "$ODOO_SERVICE" -u all --log-level=debug --stop-after-init --no-http --load=base,web,openupgrade_framework
|
||||
|
||||
echo ""
|
||||
echo "Running post-migration view validation..."
|
||||
|
||||
148
upgrade.sh
148
upgrade.sh
@@ -4,13 +4,20 @@ set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/lib/common.sh"
|
||||
|
||||
readonly LOG_FILE="${SCRIPT_DIR}/migration.log"
|
||||
if [[ -z "${_MIGRATION_LOGGING:-}" ]]; then
|
||||
rm -f "$LOG_FILE"
|
||||
export _MIGRATION_LOGGING=1
|
||||
exec script -q -c "$(printf '%q ' "$0" "$@")" "$LOG_FILE"
|
||||
fi
|
||||
|
||||
####################
|
||||
# USAGE & ARGUMENTS
|
||||
####################
|
||||
|
||||
usage() {
|
||||
cat <<EOF >&2
|
||||
Usage: $0 <origin_version> <final_version> <db_name> <service_name>
|
||||
Usage: $0 <origin_version> <final_version> <db_name> <service_name> [--resume-from|-r <version>]
|
||||
|
||||
Arguments:
|
||||
origin_version Origin Odoo version number (e.g., 12 for version 12.0)
|
||||
@@ -18,14 +25,22 @@ Arguments:
|
||||
db_name Name of the database to migrate
|
||||
service_name Name of the origin Odoo service (docker compose service)
|
||||
|
||||
Example:
|
||||
$0 14 16 elabore_20241208 odoo14
|
||||
Options:
|
||||
--resume-from, -r <version>
|
||||
Resume migration from an already-migrated intermediate
|
||||
database (e.g., ou15). Skips the initial DB copy and
|
||||
prepare_db.sh phases. The intermediate DB must exist.
|
||||
|
||||
Examples:
|
||||
$0 14 18 elabore_20241208 odoo14
|
||||
$0 14 18 elabore_20241208 odoo14 --resume-from 15
|
||||
$0 14 18 elabore_20241208 odoo14 -r 15
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
if [[ $# -lt 4 ]]; then
|
||||
log_error "Missing arguments. Expected 4, got $#."
|
||||
log_error "Missing arguments. Expected at least 4, got $#."
|
||||
usage
|
||||
fi
|
||||
|
||||
@@ -38,11 +53,50 @@ readonly FINAL_VERSION
|
||||
readonly ORIGIN_DB_NAME="$3"
|
||||
readonly ORIGIN_SERVICE_NAME="$4"
|
||||
|
||||
# Parse optional --resume-from / -r flag
|
||||
RESUME_FROM_VERSION=""
|
||||
shift 4
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--resume-from|-r)
|
||||
if [[ $# -lt 2 ]]; then
|
||||
log_error "Option '$1' requires a version number argument."
|
||||
usage
|
||||
fi
|
||||
RESUME_FROM_VERSION="$2"
|
||||
shift 2
|
||||
;;
|
||||
*)
|
||||
log_error "Unknown option: '$1'"
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
done
|
||||
readonly RESUME_FROM_VERSION
|
||||
|
||||
readonly COPY_DB_NAME="ou${ORIGIN_VERSION}"
|
||||
export FINALE_DB_NAME="ou${FINAL_VERSION}"
|
||||
readonly FINALE_DB_NAME
|
||||
readonly FINALE_SERVICE_NAME="${FINALE_DB_NAME}"
|
||||
|
||||
# Validate --resume-from value if provided
|
||||
if [[ -n "$RESUME_FROM_VERSION" ]]; then
|
||||
if ! [[ "$RESUME_FROM_VERSION" =~ ^[0-9]+$ ]]; then
|
||||
log_error "--resume-from value must be a numeric version number (got: '$RESUME_FROM_VERSION')."
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$RESUME_FROM_VERSION" -le "$ORIGIN_VERSION" ]]; then
|
||||
log_error "--resume-from ($RESUME_FROM_VERSION) must be strictly greater than origin version ($ORIGIN_VERSION)."
|
||||
log_error "To start a fresh migration, run without --resume-from."
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$RESUME_FROM_VERSION" -ge "$FINAL_VERSION" ]]; then
|
||||
log_error "--resume-from ($RESUME_FROM_VERSION) must be strictly less than final version ($FINAL_VERSION)."
|
||||
log_error "Nothing to migrate: checkpoint is at or beyond the target version."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
readarray -t postgres_containers < <(docker ps --format '{{.Names}}' | grep postgres || true)
|
||||
|
||||
if [[ ${#postgres_containers[@]} -eq 0 ]]; then
|
||||
@@ -61,8 +115,13 @@ readonly POSTGRES_SERVICE_NAME
|
||||
log_step "INPUT PARAMETERS"
|
||||
log_info "Origin version .......... $ORIGIN_VERSION"
|
||||
log_info "Final version ........... $FINAL_VERSION"
|
||||
log_info "Origin DB name ........... $ORIGIN_DB_NAME"
|
||||
log_info "Origin DB name .......... $ORIGIN_DB_NAME"
|
||||
log_info "Origin service name ..... $ORIGIN_SERVICE_NAME"
|
||||
if [[ -n "$RESUME_FROM_VERSION" ]]; then
|
||||
log_info "Resume from version ..... $RESUME_FROM_VERSION (ou${RESUME_FROM_VERSION})"
|
||||
else
|
||||
log_info "Resume from version ..... none (full migration)"
|
||||
fi
|
||||
|
||||
log_step "COMPUTED GLOBAL VARIABLES"
|
||||
log_info "Copy DB name ............. $COPY_DB_NAME"
|
||||
@@ -74,20 +133,42 @@ log_info "Postgres service name .... $POSTGRES_SERVICE_NAME"
|
||||
|
||||
log_step "CHECKS ALL NEEDED COMPONENTS ARE AVAILABLE"
|
||||
|
||||
db_exists=$(docker exec -it -u 70 "$POSTGRES_SERVICE_NAME" psql -tc "SELECT 1 FROM pg_database WHERE datname = '$ORIGIN_DB_NAME'" | tr -d '[:space:]')
|
||||
if [[ "$db_exists" ]]; then
|
||||
log_info "Database '$ORIGIN_DB_NAME' found."
|
||||
else
|
||||
log_error "Database '$ORIGIN_DB_NAME' not found in the local postgres service. Please add it and restart the upgrade process."
|
||||
exit 1
|
||||
fi
|
||||
if [[ -n "$RESUME_FROM_VERSION" ]]; then
|
||||
readonly RESUME_DB_NAME="ou${RESUME_FROM_VERSION}"
|
||||
|
||||
filestore_path="${DATASTORE_PATH}/${ORIGIN_SERVICE_NAME}/${FILESTORE_SUBPATH}/${ORIGIN_DB_NAME}"
|
||||
if [[ -d "$filestore_path" ]]; then
|
||||
log_info "Filestore '$filestore_path' found."
|
||||
resume_db_exists=$(docker exec -u 70 "$POSTGRES_SERVICE_NAME" psql -tc "SELECT 1 FROM pg_database WHERE datname = '${RESUME_DB_NAME}'" | tr -d '[:space:]')
|
||||
if [[ "$resume_db_exists" ]]; then
|
||||
log_info "Checkpoint database '${RESUME_DB_NAME}' found."
|
||||
else
|
||||
log_error "Checkpoint database '${RESUME_DB_NAME}' not found in the local postgres service."
|
||||
log_error "Ensure the migration up to version ${RESUME_FROM_VERSION} completed successfully before resuming."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
resume_filestore_path="${DATASTORE_PATH}/${RESUME_DB_NAME}/${FILESTORE_SUBPATH}/${RESUME_DB_NAME}"
|
||||
if [[ -d "$resume_filestore_path" ]]; then
|
||||
log_info "Checkpoint filestore '${resume_filestore_path}' found."
|
||||
else
|
||||
log_error "Checkpoint filestore '${resume_filestore_path}' not found."
|
||||
log_error "Ensure the filestore for '${RESUME_DB_NAME}' is intact before resuming."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
log_error "Filestore '$filestore_path' not found, please add it and restart the upgrade process."
|
||||
exit 1
|
||||
db_exists=$(docker exec -u 70 "$POSTGRES_SERVICE_NAME" psql -tc "SELECT 1 FROM pg_database WHERE datname = '$ORIGIN_DB_NAME'" | tr -d '[:space:]')
|
||||
if [[ "$db_exists" ]]; then
|
||||
log_info "Database '$ORIGIN_DB_NAME' found."
|
||||
else
|
||||
log_error "Database '$ORIGIN_DB_NAME' not found in the local postgres service. Please add it and restart the upgrade process."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
filestore_path="${DATASTORE_PATH}/${ORIGIN_SERVICE_NAME}/${FILESTORE_SUBPATH}/${ORIGIN_DB_NAME}"
|
||||
if [[ -d "$filestore_path" ]]; then
|
||||
log_info "Filestore '$filestore_path' found."
|
||||
else
|
||||
log_error "Filestore '$filestore_path' not found, please add it and restart the upgrade process."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
log_step "LAUNCH VIRGIN ODOO IN FINAL VERSION"
|
||||
@@ -102,24 +183,36 @@ run_compose --debug run "$FINALE_SERVICE_NAME" -i base --stop-after-init --no-ht
|
||||
|
||||
log_info "Model database in final Odoo version created."
|
||||
|
||||
log_step "COPY ORIGINAL COMPONENTS"
|
||||
if [[ -z "$RESUME_FROM_VERSION" ]]; then
|
||||
log_step "COPY ORIGINAL COMPONENTS"
|
||||
|
||||
copy_database "$ORIGIN_DB_NAME" "$COPY_DB_NAME" "$COPY_DB_NAME"
|
||||
log_info "Original database copied to ${COPY_DB_NAME}@${COPY_DB_NAME}."
|
||||
copy_database "$ORIGIN_DB_NAME" "$COPY_DB_NAME" "$COPY_DB_NAME"
|
||||
log_info "Original database copied to ${COPY_DB_NAME}@${COPY_DB_NAME}."
|
||||
|
||||
copy_filestore "$ORIGIN_SERVICE_NAME" "$ORIGIN_DB_NAME" "$COPY_DB_NAME" "$COPY_DB_NAME"
|
||||
log_info "Original filestore copied."
|
||||
copy_filestore "$ORIGIN_SERVICE_NAME" "$ORIGIN_DB_NAME" "$COPY_DB_NAME" "$COPY_DB_NAME"
|
||||
log_info "Original filestore copied."
|
||||
else
|
||||
log_step "COPY ORIGINAL COMPONENTS — SKIPPED (resuming from checkpoint ou${RESUME_FROM_VERSION})"
|
||||
fi
|
||||
|
||||
|
||||
log_step "PATH OF MIGRATION"
|
||||
|
||||
readarray -t versions < <(seq $((ORIGIN_VERSION + 1)) "$FINAL_VERSION")
|
||||
log_info "Migration path is ${versions[*]}"
|
||||
if [[ -n "$RESUME_FROM_VERSION" ]]; then
|
||||
readarray -t versions < <(seq $((RESUME_FROM_VERSION + 1)) "$FINAL_VERSION")
|
||||
log_info "Resuming migration from ou${RESUME_FROM_VERSION} — path is ${versions[*]}"
|
||||
else
|
||||
readarray -t versions < <(seq $((ORIGIN_VERSION + 1)) "$FINAL_VERSION")
|
||||
log_info "Migration path is ${versions[*]}"
|
||||
fi
|
||||
|
||||
|
||||
log_step "DATABASE PREPARATION"
|
||||
|
||||
"${SCRIPT_DIR}/scripts/prepare_db.sh" "$COPY_DB_NAME" "$COPY_DB_NAME" "$FINALE_DB_NAME" "$FINALE_SERVICE_NAME"
|
||||
if [[ -z "$RESUME_FROM_VERSION" ]]; then
|
||||
log_step "DATABASE PREPARATION"
|
||||
"${SCRIPT_DIR}/scripts/prepare_db.sh" "$COPY_DB_NAME" "$COPY_DB_NAME" "$FINALE_DB_NAME" "$FINALE_SERVICE_NAME"
|
||||
else
|
||||
log_step "DATABASE PREPARATION — SKIPPED (resuming from checkpoint ou${RESUME_FROM_VERSION})"
|
||||
fi
|
||||
|
||||
|
||||
log_step "UPGRADE PROCESS"
|
||||
@@ -139,3 +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"
|
||||
log_info "Full logs available at: ${LOG_FILE}"
|
||||
|
||||
@@ -8,6 +8,7 @@ obsolete:
|
||||
# Modules merged into Odoo Core in version 15.0
|
||||
merged_in_core:
|
||||
- project_category
|
||||
- project_deadline
|
||||
|
||||
# Modules renamed in version 15.0
|
||||
renamed:
|
||||
|
||||
@@ -6,6 +6,7 @@ obsolete:
|
||||
# Modules merged into Odoo Core in version 18.0
|
||||
merged_in_core:
|
||||
- account_payment_partner
|
||||
- hr_expense_report_merge_attachment
|
||||
|
||||
# Modules renamed in version 18.0
|
||||
renamed:
|
||||
|
||||
@@ -116,7 +116,7 @@ BEGIN
|
||||
|
||||
RAISE NOTICE 'Starting account_payment_batch_oca migration...';
|
||||
|
||||
IF EXISTS (SELECT FROM information_schema.columns
|
||||
IF EXISTS (SELECT FROM information_schema.columns
|
||||
WHERE table_name = 'account_payment_method' AND column_name = 'payment_order_only') THEN
|
||||
UPDATE account_payment_method
|
||||
SET payment_order_ok = payment_order_only
|
||||
@@ -136,7 +136,7 @@ BEGIN
|
||||
WHERE apml.old_payment_mode_id IS NOT NULL
|
||||
AND apm.id = apml.old_payment_mode_id;
|
||||
|
||||
IF EXISTS (SELECT FROM information_schema.tables
|
||||
IF EXISTS (SELECT FROM information_schema.tables
|
||||
WHERE table_name = 'account_journal_account_payment_method_line_rel') THEN
|
||||
DELETE FROM account_journal_account_payment_method_line_rel
|
||||
WHERE account_payment_method_line_id IN (
|
||||
@@ -173,4 +173,236 @@ else
|
||||
echo "Table account_payment_mode not found, skipping bank-payment migration."
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# FIX: stock_picking name='/' and missing POS picking type sequences
|
||||
#
|
||||
# Two issues can occur after migration:
|
||||
#
|
||||
# 1. stock_picking records with name='/' violate the new V18 unique constraint
|
||||
# stock_picking_name_uniq (name, company_id). In previous versions this
|
||||
# constraint did not exist, so multiple pickings could share name='/'.
|
||||
#
|
||||
# 2. POS picking types (pos_type_id on stock_warehouse) may lack a sequence_id.
|
||||
# In V18, stock.picking.create() assigns the name from picking_type.sequence_id,
|
||||
# but if sequence_id is NULL the name stays '/' and the unique constraint is
|
||||
# violated on every new POS payment.
|
||||
#
|
||||
# The standard _create_missing_pos_picking_types() only fixes warehouses where
|
||||
# pos_type_id is NULL — it does NOT fix existing pos_type_id records that are
|
||||
# missing their sequence_id.
|
||||
#
|
||||
# Only executed if stock module tables exist.
|
||||
# ============================================================================
|
||||
|
||||
STOCK_PICKING_TABLE_EXISTS=$(query_postgres_container "SELECT COUNT(*) FROM information_schema.tables WHERE table_name = 'stock_picking';" ou18 2>/dev/null | grep -E '^\s*[0-9]+' | tr -d ' ' || echo "0")
|
||||
|
||||
if [ "$STOCK_PICKING_TABLE_EXISTS" -gt 0 ]; then
|
||||
echo "stock_picking table exists, proceeding with POS picking fixes..."
|
||||
|
||||
# --- Step 1: Rename stock_picking records with name='/' ---
|
||||
FIX_SLASH_PICKING_NAMES_SQL=$(cat <<'EOF'
|
||||
DO $$
|
||||
DECLARE
|
||||
slash_count INTEGER;
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'stock_picking') THEN
|
||||
RAISE NOTICE 'stock_picking table not found, skipping slash name fix';
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
SELECT COUNT(*) INTO slash_count FROM stock_picking WHERE name = '/';
|
||||
|
||||
IF slash_count > 0 THEN
|
||||
UPDATE stock_picking SET name = 'MIGRATED-' || id WHERE name = '/';
|
||||
RAISE NOTICE 'Renamed % stock_picking record(s) with name=/ to MIGRATED-<id>', slash_count;
|
||||
ELSE
|
||||
RAISE NOTICE 'No stock_picking with name=/ found, nothing to rename';
|
||||
END IF;
|
||||
END $$;
|
||||
EOF
|
||||
)
|
||||
echo "Fixing stock_picking names with '/'..."
|
||||
query_postgres_container "$FIX_SLASH_PICKING_NAMES_SQL" ou18 || exit 1
|
||||
|
||||
# --- Step 2: Create missing ir.sequence for POS picking types without sequence ---
|
||||
FIX_POS_PICKING_TYPE_SEQUENCES_SQL=$(cat <<'EOF'
|
||||
DO $$
|
||||
DECLARE
|
||||
pt_rec RECORD;
|
||||
seq_id INTEGER;
|
||||
seq_name TEXT;
|
||||
seq_prefix TEXT;
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'stock_picking_type') THEN
|
||||
RAISE NOTICE 'stock_picking_type table not found, skipping POS sequence fix';
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT FROM information_schema.columns
|
||||
WHERE table_name = 'stock_warehouse' AND column_name = 'pos_type_id') THEN
|
||||
RAISE NOTICE 'stock_warehouse.pos_type_id column not found (point_of_sale not installed), skipping POS sequence fix';
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
FOR pt_rec IN
|
||||
SELECT spt.id AS picking_type_id,
|
||||
spt.name AS picking_type_name,
|
||||
COALESCE(spt.sequence_code, 'POS') AS sequence_code,
|
||||
sw.code AS warehouse_code,
|
||||
sw.name AS warehouse_name,
|
||||
sw.company_id
|
||||
FROM stock_picking_type spt
|
||||
JOIN stock_warehouse sw ON sw.pos_type_id = spt.id
|
||||
WHERE spt.sequence_id IS NULL OR spt.sequence_id = 0
|
||||
LOOP
|
||||
seq_prefix := pt_rec.warehouse_code || '/' || pt_rec.sequence_code || '/';
|
||||
seq_name := pt_rec.warehouse_name || ' Picking POS';
|
||||
|
||||
INSERT INTO ir_sequence (
|
||||
name, prefix, padding, company_id, implementation,
|
||||
number_increment, number_next, active
|
||||
) VALUES (
|
||||
seq_name, seq_prefix, 5, pt_rec.company_id, 'standard',
|
||||
1, 1, true
|
||||
) RETURNING id INTO seq_id;
|
||||
|
||||
UPDATE stock_picking_type
|
||||
SET sequence_id = seq_id
|
||||
WHERE id = pt_rec.picking_type_id;
|
||||
|
||||
RAISE NOTICE 'Created ir.sequence % (%) for POS picking type % (ID:%) on warehouse %',
|
||||
seq_name, seq_id, pt_rec.picking_type_name, pt_rec.picking_type_id, pt_rec.warehouse_name;
|
||||
END LOOP;
|
||||
END $$;
|
||||
EOF
|
||||
)
|
||||
echo "Creating missing POS picking type sequences..."
|
||||
query_postgres_container "$FIX_POS_PICKING_TYPE_SEQUENCES_SQL" ou18 || exit 1
|
||||
|
||||
# --- Step 3: Create missing PostgreSQL sequences for ir.sequence records ---
|
||||
# When ir.sequence records are created via SQL INSERT (not ORM), the underlying
|
||||
# PostgreSQL sequence (ir_sequence_NNN) is NOT created. This causes errors when
|
||||
# Odoo tries to read or use the sequence.
|
||||
FIX_MISSING_PG_SEQUENCES_SQL=$(cat <<'EOF'
|
||||
DO $$
|
||||
DECLARE
|
||||
seq_rec RECORD;
|
||||
seq_pg_name TEXT;
|
||||
seq_count INTEGER;
|
||||
BEGIN
|
||||
seq_count := 0;
|
||||
|
||||
FOR seq_rec IN
|
||||
SELECT id, number_increment, number_next
|
||||
FROM ir_sequence
|
||||
WHERE implementation = 'standard'
|
||||
LOOP
|
||||
seq_pg_name := 'ir_sequence_' || lpad(seq_rec.id::text, 3, '0');
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_class
|
||||
WHERE relkind = 'S' AND relname = seq_pg_name
|
||||
) THEN
|
||||
EXECUTE format('CREATE SEQUENCE %I INCREMENT BY %s START WITH %s',
|
||||
seq_pg_name, seq_rec.number_increment, seq_rec.number_next);
|
||||
seq_count := seq_count + 1;
|
||||
RAISE NOTICE 'Created PostgreSQL sequence %', seq_pg_name;
|
||||
END IF;
|
||||
END LOOP;
|
||||
|
||||
IF seq_count > 0 THEN
|
||||
RAISE NOTICE 'Created % missing PostgreSQL sequence(s)', seq_count;
|
||||
ELSE
|
||||
RAISE NOTICE 'All PostgreSQL sequences already exist, nothing to create';
|
||||
END IF;
|
||||
END $$;
|
||||
EOF
|
||||
)
|
||||
echo "Creating missing PostgreSQL sequences for ir.sequence records..."
|
||||
query_postgres_container "$FIX_MISSING_PG_SEQUENCES_SQL" ou18 || exit 1
|
||||
|
||||
# --- Step 4: Grant permissions on newly created PostgreSQL sequences ---
|
||||
# Sequences created above are owned by the current DB user, but we ensure
|
||||
# the Odoo DB user has proper access (needed when created by a different superuser).
|
||||
GRANT_PG_SEQUENCES_SQL=$(cat <<'EOF'
|
||||
DO $$
|
||||
DECLARE
|
||||
seq_rec RECORD;
|
||||
seq_pg_name TEXT;
|
||||
db_user TEXT;
|
||||
grant_count INTEGER;
|
||||
BEGIN
|
||||
-- Determine the Odoo database user from the current connection
|
||||
SELECT current_user INTO db_user;
|
||||
grant_count := 0;
|
||||
|
||||
FOR seq_rec IN
|
||||
SELECT id
|
||||
FROM ir_sequence
|
||||
WHERE implementation = 'standard'
|
||||
LOOP
|
||||
seq_pg_name := 'ir_sequence_' || lpad(seq_rec.id::text, 3, '0');
|
||||
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM pg_class
|
||||
WHERE relkind = 'S' AND relname = seq_pg_name
|
||||
) THEN
|
||||
BEGIN
|
||||
EXECUTE format('GRANT ALL ON SEQUENCE %I TO %I', seq_pg_name, db_user);
|
||||
grant_count := grant_count + 1;
|
||||
EXCEPTION WHEN insufficient_privilege THEN
|
||||
RAISE NOTICE 'Cannot GRANT on % (not owner), skipping', seq_pg_name;
|
||||
END;
|
||||
END IF;
|
||||
END LOOP;
|
||||
|
||||
RAISE NOTICE 'Granted permissions on % PostgreSQL sequence(s) to %', grant_count, db_user;
|
||||
END $$;
|
||||
EOF
|
||||
)
|
||||
echo "Granting permissions on PostgreSQL sequences..."
|
||||
query_postgres_container "$GRANT_PG_SEQUENCES_SQL" ou18 || exit 1
|
||||
|
||||
# --- Step 5: Verification ---
|
||||
VERIFY_POS_FIXES_SQL=$(cat <<'EOF'
|
||||
DO $$
|
||||
DECLARE
|
||||
slash_count INTEGER;
|
||||
missing_seq_count INTEGER;
|
||||
BEGIN
|
||||
-- Check remaining pickings with name='/'
|
||||
IF EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'stock_picking') THEN
|
||||
SELECT COUNT(*) INTO slash_count FROM stock_picking WHERE name = '/';
|
||||
IF slash_count > 0 THEN
|
||||
RAISE WARNING 'Still % stock_picking record(s) with name=/', slash_count;
|
||||
ELSE
|
||||
RAISE NOTICE 'OK: No stock_picking with name=/ remaining';
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
-- Check POS picking types without sequence
|
||||
IF EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'stock_picking_type')
|
||||
AND EXISTS (SELECT FROM information_schema.columns
|
||||
WHERE table_name = 'stock_warehouse' AND column_name = 'pos_type_id') THEN
|
||||
SELECT COUNT(*) INTO missing_seq_count
|
||||
FROM stock_picking_type spt
|
||||
JOIN stock_warehouse sw ON sw.pos_type_id = spt.id
|
||||
WHERE spt.sequence_id IS NULL OR spt.sequence_id = 0;
|
||||
|
||||
IF missing_seq_count > 0 THEN
|
||||
RAISE WARNING 'Still % POS picking type(s) without sequence_id', missing_seq_count;
|
||||
ELSE
|
||||
RAISE NOTICE 'OK: All POS picking types have a sequence_id';
|
||||
END IF;
|
||||
END IF;
|
||||
END $$;
|
||||
EOF
|
||||
)
|
||||
echo "Verifying POS picking fixes..."
|
||||
query_postgres_container "$VERIFY_POS_FIXES_SQL" ou18 || exit 1
|
||||
|
||||
else
|
||||
echo "stock_picking table not found, skipping POS picking fixes."
|
||||
fi
|
||||
|
||||
echo "Post migration to 18.0 completed!"
|
||||
|
||||
@@ -127,6 +127,8 @@ EOF
|
||||
# Execute SQL pre-migration commands
|
||||
PRE_MIGRATE_SQL=$(cat <<'EOF'
|
||||
UPDATE account_analytic_plan SET default_applicability=NULL WHERE default_applicability='optional';
|
||||
DELETE FROM ir_ui_view WHERE model = 'res.config.settings';
|
||||
|
||||
EOF
|
||||
)
|
||||
echo "SQL command = $PRE_MIGRATE_SQL"
|
||||
|
||||
Reference in New Issue
Block a user