feat: Add new scripts for Odoo MO/QC cleanup, custom addon Git operations, and module upgrades, and improve module path resolution in existing scripts.

This commit is contained in:
Suherdy Yacob 2026-02-12 15:17:27 +07:00
parent 8bc083b84e
commit b35ad10c51
5 changed files with 323 additions and 0 deletions

129
delete_old_mo_qc.py Normal file
View File

@ -0,0 +1,129 @@
import sys
import os
import time
from datetime import datetime, timedelta
sys.path.append(os.getcwd())
# Try to import odoo, if not found, assume we are in odoo-bin shell or need path
try:
import odoo
from odoo import api, SUPERUSER_ID
from odoo.tools import config
except ImportError as e:
# Check if due to missing dependency like passlib
if "No module named" in str(e):
print("!" * 80)
print(f"ERROR: Missing dependency: {e}")
print("Please run this script using the Python executable that runs Odoo.")
print("Example: /path/to/venv/bin/python delete_old_mo_qc.py")
print("!" * 80)
sys.exit(1)
current_dir = os.path.dirname(os.path.abspath(__file__))
# Try adding current_dir/odoo to path if not already covered by os.getcwd() fix above
# But usually os.getcwd() should cover it if running from community/ folder.
if os.path.exists(os.path.join(current_dir, 'odoo')):
sys.path.append(os.path.join(current_dir, 'odoo'))
else:
sys.path.append(current_dir)
import odoo
from odoo import api, SUPERUSER_ID
from odoo.tools import config
def delete_old_records():
print("Starting Delete Old MO/QC Script...")
import argparse
parser = argparse.ArgumentParser(description='Delete MOs and Quality Checks older than 1 day.')
parser.add_argument('db_name', help='Database name')
parser.add_argument('-c', '--config', help='Path to odoo.conf', default=None)
args = parser.parse_args()
db_name = args.db_name
# Load config if provided or default exists
conf_path = args.config
if conf_path:
if os.path.exists(conf_path):
config.parse_config(['-c', conf_path])
else:
print(f"WARNING: Config file '{conf_path}' not found.")
elif os.path.exists('odoo.conf'):
config.parse_config(['-c', 'odoo.conf'])
print(f"Connecting to database: {db_name}")
try:
registry = odoo.registry(db_name)
except AttributeError:
from odoo.modules.registry import Registry
registry = Registry.new(db_name)
with registry.cursor() as cr:
env = api.Environment(cr, SUPERUSER_ID, {})
# Calculate Cutoff Date
cutoff_date = datetime.now() - timedelta(days=1)
print(f"Cutoff Date: {cutoff_date}")
# 1. Find Old Quality Checks (NOT passed/failed)
print("\nSearching for old Quality Checks...")
# quality_state can be 'none', 'pass', 'fail'
domain_qc = [('create_date', '<', cutoff_date), ('quality_state', '=', 'none')]
qcs = env['quality.check'].search(domain_qc)
print(f"Found {len(qcs)} Quality Checks strictly older than 1 day (and not done).")
# 2. Find Old Manufacturing Orders (NOT done)
print("\nSearching for old Manufacturing Orders...")
# state can be 'draft', 'confirmed', 'progress', 'to_close', 'done', 'cancel'
# We delete anything NOT done (and maybe not cancel if user wants clean up, but user said 'skip done')
# Safer to skip 'cancel' too if we want pure cleanup of abandoned work? User said "skip done".
# But 'cancel' is safe to delete usually. Let's stick to != 'done'.
start_domain_mo = [('create_date', '<', cutoff_date), ('state', '!=', 'done')]
mos = env['mrp.production'].search(start_domain_mo)
print(f"Found {len(mos)} Manufacturing Orders strictly older than 1 day (and not done).")
if not qcs and not mos:
print("\nNothing to delete.")
return
print("\n---------------------------------------------------")
print("Dry run complete. No changes have been committed yet.")
print("WARNING: This will PERMANENTLY DELETE the following records:")
print(f" - {len(qcs)} Quality Checks")
print(f" - {len(mos)} Manufacturing Orders")
user_input = input("Do you want to PROCEED with DELETION and COMMIT changes? (yes/no): ")
if user_input.lower() == 'yes':
try:
if qcs:
print(f"Deleting {len(qcs)} Quality Checks...")
qcs.unlink()
print(" Success.")
if mos:
print(f"Deleting {len(mos)} Manufacturing Orders...")
# MO deletions might be blocked if state is 'done'.
# We attempt unlink, if it fails, Odoo raises UserError.
mos.unlink()
print(" Success.")
cr.commit()
print("Changes COMMITTED.")
except Exception as e:
cr.rollback()
print(f"ERROR during deletion: {e}")
print("Transaction ROLLED BACK. No changes persisted.")
else:
cr.rollback()
print("Operation CANCELLED (Rolled back).")
print("Done.")
if __name__ == '__main__':
delete_old_records()

View File

@ -2,6 +2,8 @@ import sys
import os
import time
sys.path.append(os.getcwd())
# Try to import odoo, if not found, assume we are in odoo-bin shell or need path
try:
import odoo

30
pull_all_addons.sh Normal file
View File

@ -0,0 +1,30 @@
#!/bin/bash
# Define the customaddons directory relative to the script location
CUSTOM_ADDONS_DIR="$(dirname "$0")/customaddons"
if [ ! -d "$CUSTOM_ADDONS_DIR" ]; then
echo "Error: customaddons directory not found at $CUSTOM_ADDONS_DIR"
exit 1
fi
echo "Starting git pull for all modules in $CUSTOM_ADDONS_DIR..."
# Iterate over each subdirectory in customaddons
for dir in "$CUSTOM_ADDONS_DIR"/*; do
if [ -d "$dir" ]; then
# Check if it's a git repository
if [ -d "$dir/.git" ]; then
echo "--------------------------------------------------"
echo "Pulling updates for $(basename "$dir")..."
(cd "$dir" && git pull)
else
# Optional: Uncomment the line below if you want to be notified about non-git folders
# echo "Skipping $(basename "$dir") - Not a git repository"
:
fi
fi
done
echo "--------------------------------------------------"
echo "All done."

View File

@ -2,6 +2,8 @@ import sys
import os
import time
sys.path.append(os.getcwd())
# Try to import odoo, if not found, assume we are in odoo-bin shell or need path
try:
import odoo

160
upgrade_custom_addons.py Normal file
View File

@ -0,0 +1,160 @@
import os
import sys
import argparse
import subprocess
import configparser
import psycopg2
def get_custom_addons_modules(addons_path):
"""
Scans the given addons path and returns a list of directory names
that contain a __manifest__.py file.
"""
modules = []
if not os.path.exists(addons_path):
print(f"Error: Addons path '{addons_path}' does not exist.")
return []
for item in os.listdir(addons_path):
item_path = os.path.join(addons_path, item)
if os.path.isdir(item_path):
manifest_path = os.path.join(item_path, '__manifest__.py')
if os.path.exists(manifest_path):
modules.append(item)
return modules
def get_db_params(config_path):
"""
Parses the Odoo config file to extract database connection parameters.
"""
config = configparser.ConfigParser()
try:
config.read(config_path)
except Exception as e:
print(f"Error reading config file: {e}")
return {}
db_params = {}
if 'options' in config:
params = config['options']
db_params['host'] = params.get('db_host', 'localhost')
db_params['port'] = params.get('db_port', '5432')
db_params['user'] = params.get('db_user', 'odoo')
db_params['password'] = params.get('db_password', '')
return db_params
def get_installed_modules(modules_list, db_name, db_params):
"""
Connects to the database and filters the given list of modules specifically
for those that are installed (state='installed').
"""
installed_modules = []
conn_params = {
'dbname': db_name,
'user': db_params.get('user', 'odoo'),
'password': db_params.get('password', ''),
'host': db_params.get('host', 'localhost'),
'port': db_params.get('port', '5432')
}
# Filter out empty or None values to use defaults or avoid errors
conn_params = {k: v for k, v in conn_params.items() if v}
try:
conn = psycopg2.connect(**conn_params)
cur = conn.cursor()
# Check if modules are installed
if not modules_list:
return []
query = "SELECT name FROM ir_module_module WHERE state = 'installed' AND name IN %s"
cur.execute(query, (tuple(modules_list),))
rows = cur.fetchall()
installed_modules = [row[0] for row in rows]
cur.close()
conn.close()
except psycopg2.Error as e:
print(f"Database error: {e}")
print("Ensure psycopg2 matches the server configuration.")
return []
except Exception as e:
print(f"Error checking installed modules: {e}")
return []
return installed_modules
def main():
parser = argparse.ArgumentParser(description="Upgrade installed modules in customaddons for a specific database.")
parser.add_argument('-d', '--database', required=True, help="Database name to upgrade modules on.")
parser.add_argument('-c', '--config', required=True, help="Path to Odoo configuration file.")
parser.add_argument('--odoo-bin', default='./odoo/odoo-bin', help="Path to odoo-bin executable (default: ./odoo/odoo-bin)")
args = parser.parse_args()
# Determine customaddons path relative to this script
script_dir = os.path.dirname(os.path.abspath(__file__))
custom_addons_path = os.path.join(script_dir, 'customaddons')
print(f"Scanning for modules in {custom_addons_path}...")
modules = get_custom_addons_modules(custom_addons_path)
if not modules:
print("No modules found in customaddons.")
sys.exit(0)
print(f"Found {len(modules)} local modules.")
# Get DB params from config
db_params = get_db_params(args.config)
print(f"Checking installed status in database '{args.database}'...")
installed_modules = get_installed_modules(modules, args.database, db_params)
if not installed_modules:
print("None of the local modules are installed in the specified database.")
sys.exit(0)
print(f"Found {len(installed_modules)} installed modules to upgrade.")
# Construct the Odoo command
modules_str = ','.join(installed_modules)
# Check if odoo-bin exists
if not os.path.exists(args.odoo_bin) and not os.path.exists(os.path.abspath(args.odoo_bin)):
print(f"Error: odoo-bin not found at {args.odoo_bin}")
sys.exit(1)
cmd = [
sys.executable,
args.odoo_bin,
'-c', args.config,
'-d', args.database,
'-u', modules_str,
'--stop-after-init'
]
print("--------------------------------------------------")
print(f"Starting upgrade for database '{args.database}'...")
print(f"Modules to upgrade: {modules_str}")
print(f"Command: {' '.join(cmd)}")
print("--------------------------------------------------")
try:
subprocess.run(cmd, check=True)
print("--------------------------------------------------")
print("Upgrade completed successfully.")
except subprocess.CalledProcessError as e:
print("--------------------------------------------------")
print(f"Error: Upgrade failed with exit code {e.returncode}")
sys.exit(e.returncode)
except Exception as e:
print(f"An unexpected error occurred: {e}")
sys.exit(1)
if __name__ == "__main__":
main()