feat: Add a collection of Odoo scripts to migrate company data, clone journals and payment methods, and address data inconsistencies.
This commit is contained in:
parent
4cdbcb0bd7
commit
861ba677e4
33
check_pos_rounding.py
Normal file
33
check_pos_rounding.py
Normal file
@ -0,0 +1,33 @@
|
||||
import sys
|
||||
|
||||
def check_pos_rounding(env):
|
||||
source_name = 'Kedai Kipas 58 Rungkut'
|
||||
target_name = 'Kedai Kipas 58 Tenggilis'
|
||||
|
||||
source_company = env['res.company'].search([('name', 'ilike', source_name)], limit=1)
|
||||
target_company = env['res.company'].search([('name', 'ilike', target_name)], limit=1)
|
||||
|
||||
if not source_company or not target_company:
|
||||
print(f"Could not find one or both companies: '{source_name}', '{target_name}'")
|
||||
return
|
||||
|
||||
print("--- Rungkut POS Configs (Source) ---")
|
||||
source_configs = env['pos.config'].with_context(active_test=False).search([
|
||||
('company_id', '=', source_company.id)
|
||||
])
|
||||
for c in source_configs:
|
||||
print(f"[{c.name}] cash_rounding: {c.cash_rounding}")
|
||||
print(f"[{c.name}] rounding_method: {c.rounding_method.name if c.rounding_method else 'None'} (ID: {c.rounding_method.id if c.rounding_method else 'None'})")
|
||||
print(f"[{c.name}] only_round_cash_method: {c.only_round_cash_method}")
|
||||
|
||||
print("\n--- Tenggilis POS Configs (Target) ---")
|
||||
target_configs = env['pos.config'].with_context(active_test=False).search([
|
||||
('company_id', '=', target_company.id)
|
||||
])
|
||||
for c in target_configs:
|
||||
print(f"[{c.name}] cash_rounding: {c.cash_rounding}")
|
||||
print(f"[{c.name}] rounding_method: {c.rounding_method.name if c.rounding_method else 'None'} (ID: {c.rounding_method.id if c.rounding_method else 'None'})")
|
||||
print(f"[{c.name}] only_round_cash_method: {c.only_round_cash_method}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
check_pos_rounding(env)
|
||||
49
cleanup_cloned_data_tenggilis.py
Normal file
49
cleanup_cloned_data_tenggilis.py
Normal file
@ -0,0 +1,49 @@
|
||||
import sys
|
||||
|
||||
def cleanup(env):
|
||||
target_company = env['res.company'].search([('name', 'ilike', 'Tenggilis')], limit=1)
|
||||
|
||||
if not target_company:
|
||||
print("Tenggilis company not found")
|
||||
return
|
||||
|
||||
print("Cleaning up copied payment methods...")
|
||||
methods = env['pos.payment.method'].with_context(active_test=False).search([
|
||||
('company_id', '=', target_company.id),
|
||||
('create_date', '>=', '2026-02-24')
|
||||
])
|
||||
num_methods = len(methods)
|
||||
for m in methods:
|
||||
print(f" Deleting Payment Method: {m.name}")
|
||||
methods.unlink()
|
||||
|
||||
print("Cleaning up copied journals...")
|
||||
journals = env['account.journal'].with_context(active_test=False).search([
|
||||
('company_id', '=', target_company.id),
|
||||
('create_date', '>=', '2026-02-24')
|
||||
])
|
||||
num_journals = len(journals)
|
||||
for j in journals:
|
||||
print(f" Deleting Journal: {j.name}")
|
||||
journals.unlink()
|
||||
|
||||
print("Cleaning up (copy) accounts...")
|
||||
accounts = env['account.account'].with_context(active_test=False).search([
|
||||
('company_id', '=', target_company.id),
|
||||
'|',
|
||||
('name', 'ilike', '(copy)'),
|
||||
('create_date', '>=', '2026-02-24')
|
||||
])
|
||||
num_accounts = len(accounts)
|
||||
for a in accounts:
|
||||
print(f" Deleting Account: {a.name} ({a.code})")
|
||||
accounts.unlink()
|
||||
|
||||
print(f"Cleanup complete. Deleted {num_methods} methods, {num_journals} journals, {num_accounts} accounts.")
|
||||
env.cr.commit()
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
cleanup(env)
|
||||
except NameError:
|
||||
print("Run via odoo shell")
|
||||
70
clone_accounting_journals.py
Normal file
70
clone_accounting_journals.py
Normal file
@ -0,0 +1,70 @@
|
||||
import sys
|
||||
|
||||
def clone_journals(env):
|
||||
source_name = 'Kedai Kipas 58 Rungkut'
|
||||
target_name = 'Kedai Kipas 58 Tenggilis'
|
||||
|
||||
source_company = env['res.company'].search([('name', 'ilike', source_name)], limit=1)
|
||||
target_company = env['res.company'].search([('name', 'ilike', target_name)], limit=1)
|
||||
|
||||
if not source_company or not target_company:
|
||||
print(f"Could not find one or both companies: '{source_name}', '{target_name}'")
|
||||
return
|
||||
|
||||
print(f"Source: {source_company.name} (ID: {source_company.id})")
|
||||
print(f"Target: {target_company.name} (ID: {target_company.id})")
|
||||
|
||||
# Fetch all journals from the source company
|
||||
journals = env['account.journal'].with_context(active_test=False).search([
|
||||
('company_id', '=', source_company.id)
|
||||
])
|
||||
|
||||
print(f"Found {len(journals)} journals in {source_company.name} to clone.")
|
||||
|
||||
count = 0
|
||||
for journal in journals:
|
||||
# Check if a journal with the same code already exists in target
|
||||
existing = env['account.journal'].with_context(active_test=False).search([
|
||||
('code', '=', journal.code),
|
||||
('company_id', '=', target_company.id)
|
||||
], limit=1)
|
||||
|
||||
if existing:
|
||||
print(f" -> '{journal.name}' (Code: {journal.code}) already exists in {target_company.name}. Skipping.")
|
||||
continue
|
||||
|
||||
try:
|
||||
with env.cr.savepoint():
|
||||
# Explicitly pass the shared accounts so Odoo doesn't try to auto-create new ones
|
||||
copy_defaults = {
|
||||
'company_id': target_company.id,
|
||||
'default_account_id': journal.default_account_id.id if journal.default_account_id else False,
|
||||
'suspense_account_id': journal.suspense_account_id.id if journal.suspense_account_id else False,
|
||||
'profit_account_id': journal.profit_account_id.id if journal.profit_account_id else False,
|
||||
'loss_account_id': journal.loss_account_id.id if journal.loss_account_id else False,
|
||||
}
|
||||
|
||||
# Clone the journal
|
||||
new_journal = journal.copy(copy_defaults)
|
||||
|
||||
# Odoo's copy might override name/code or append "(copy)"
|
||||
# Write to ensure they are identical to source
|
||||
new_journal.write({
|
||||
'name': journal.name,
|
||||
'code': journal.code,
|
||||
})
|
||||
print(f" -> Cloned '{journal.name}' [{journal.code}] (New ID: {new_journal.id})")
|
||||
count += 1
|
||||
except Exception as e:
|
||||
print(f" -> Failed to clone '{journal.name}' [{journal.code}]: {e}")
|
||||
|
||||
print(f"Committing changes... (Cloned {count} journals)")
|
||||
env.cr.commit()
|
||||
print("Cloning complete!")
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
clone_journals(env)
|
||||
except NameError:
|
||||
print("Please run this script using Odoo shell:")
|
||||
print("Example: ./.venv/bin/python ./odoo/odoo-bin shell -d <DB_NAME> -c odoo.conf < scripts/clone_accounting_journals.py")
|
||||
112
clone_cash_rounding_pricelist.py
Normal file
112
clone_cash_rounding_pricelist.py
Normal file
@ -0,0 +1,112 @@
|
||||
import sys
|
||||
|
||||
def clone_rounding_and_pricelist(env):
|
||||
source_name = 'Kedai Kipas 58 Rungkut'
|
||||
target_name = 'Kedai Kipas 58 Tenggilis'
|
||||
|
||||
source_company = env['res.company'].search([('name', 'ilike', source_name)], limit=1)
|
||||
target_company = env['res.company'].search([('name', 'ilike', target_name)], limit=1)
|
||||
|
||||
if not source_company or not target_company:
|
||||
print(f"Could not find one or both companies: '{source_name}', '{target_name}'")
|
||||
return
|
||||
|
||||
print(f"Source: {source_company.name} (ID: {source_company.id})")
|
||||
print(f"Target: {target_company.name} (ID: {target_company.id})")
|
||||
|
||||
# Let's clone Cash Rounding. If it's owned by parent, we don't necessarily need to clone the model,
|
||||
# but let's check what roundings Rungkut has. Rungkut might share the parent's rounding.
|
||||
# The user asked to "clone cash rounding setup". This probably means the POS config settings!
|
||||
|
||||
print("\n--- Applying Cash Rounding Setup from Rungkut to Tenggilis POS ---")
|
||||
source_configs = env['pos.config'].with_context(active_test=False).search([
|
||||
('company_id', '=', source_company.id),
|
||||
('cash_rounding', '=', True) # Only grab the one that actually has it configured!
|
||||
], limit=1)
|
||||
|
||||
target_configs = env['pos.config'].with_context(active_test=False).search([
|
||||
('company_id', '=', target_company.id)
|
||||
])
|
||||
|
||||
if source_configs and target_configs:
|
||||
template_config = source_configs[0]
|
||||
for t_config in target_configs:
|
||||
t_config.write({
|
||||
'cash_rounding': template_config.cash_rounding,
|
||||
'rounding_method': template_config.rounding_method.id if template_config.rounding_method else False,
|
||||
'only_round_cash_method': template_config.only_round_cash_method,
|
||||
})
|
||||
print(f" -> Applied Cash Rounding setup to POS '{t_config.name}'")
|
||||
else:
|
||||
print(" -> Could not find POS Configs in one or both companies, skipping pos.config rounding setup.")
|
||||
|
||||
# 2. Clone Pricelists
|
||||
print("\n--- Cloning Pricelists ---")
|
||||
pricelists = env['product.pricelist'].with_context(active_test=False).search([
|
||||
('company_id', '=', source_company.id)
|
||||
])
|
||||
|
||||
count_pricelist = 0
|
||||
for pricelist in pricelists:
|
||||
existing = env['product.pricelist'].with_context(active_test=False).search([
|
||||
('name', '=', pricelist.name),
|
||||
('company_id', '=', target_company.id)
|
||||
], limit=1)
|
||||
|
||||
if existing:
|
||||
print(f" -> '{pricelist.name}' already exists in {target_company.name}. Skipping.")
|
||||
continue
|
||||
|
||||
try:
|
||||
with env.cr.savepoint():
|
||||
new_pricelist = pricelist.copy({
|
||||
'company_id': target_company.id,
|
||||
# We clear website_id to fix the website company restriction error
|
||||
'website_id': False,
|
||||
})
|
||||
# Ensure name matches exactly to avoid "(copy)"
|
||||
new_pricelist.write({'name': pricelist.name})
|
||||
print(f" -> Cloned Pricelist '{pricelist.name}' (New ID: {new_pricelist.id})")
|
||||
count_pricelist += 1
|
||||
except Exception as e:
|
||||
print(f" -> Failed to clone Pricelist '{pricelist.name}': {e}")
|
||||
|
||||
# Do Tenggilis POS configs need the cloned pricelists?
|
||||
# If the Rungkut POS had a pricelist set, we should set the equivalent in Tenggilis
|
||||
if source_configs and target_configs:
|
||||
print("\n--- Updating POS Pricelist configuration ---")
|
||||
for s_config, t_config in zip(source_configs, target_configs):
|
||||
if s_config.pricelist_id:
|
||||
# Find the Tenggilis equivalent of this pricelist
|
||||
matching_pl = env['product.pricelist'].with_context(active_test=False).search([
|
||||
('name', '=', s_config.pricelist_id.name),
|
||||
('company_id', '=', target_company.id)
|
||||
], limit=1)
|
||||
if matching_pl:
|
||||
t_config.write({'pricelist_id': matching_pl.id})
|
||||
print(f" -> Set Pricelist '{matching_pl.name}' on POS '{t_config.name}'")
|
||||
|
||||
# Also handle available pricelists
|
||||
if s_config.available_pricelist_ids:
|
||||
mapped_ids = []
|
||||
for pl in s_config.available_pricelist_ids:
|
||||
match = env['product.pricelist'].with_context(active_test=False).search([
|
||||
('name', '=', pl.name),
|
||||
('company_id', '=', target_company.id)
|
||||
], limit=1)
|
||||
if match:
|
||||
mapped_ids.append(match.id)
|
||||
if mapped_ids:
|
||||
t_config.write({'available_pricelist_ids': [(6, 0, mapped_ids)]})
|
||||
print(f" -> Set {len(mapped_ids)} Available Pricelists on POS '{t_config.name}'")
|
||||
|
||||
print(f"\nCommitting changes... (Cloned setup, {count_pricelist} pricelists)")
|
||||
env.cr.commit()
|
||||
print("Done!")
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
clone_rounding_and_pricelist(env)
|
||||
except NameError:
|
||||
print("Please run this script using Odoo shell:")
|
||||
print("Example: ./.venv/bin/python ./odoo/odoo-bin shell -d <DB_NAME> -c odoo.conf < scripts/clone_cash_rounding_pricelist.py")
|
||||
58
clone_pos_payment_methods.py
Normal file
58
clone_pos_payment_methods.py
Normal file
@ -0,0 +1,58 @@
|
||||
import sys
|
||||
|
||||
def clone_payment_methods(env):
|
||||
source_name = 'Kedai Kipas 58 Rungkut'
|
||||
target_name = 'Kedai Kipas 58 Tenggilis'
|
||||
|
||||
# Using 'ilike' to be safe with exact naming
|
||||
source_company = env['res.company'].search([('name', 'ilike', source_name)], limit=1)
|
||||
target_company = env['res.company'].search([('name', 'ilike', target_name)], limit=1)
|
||||
|
||||
if not source_company or not target_company:
|
||||
print(f"Could not find one or both companies: '{source_name}', '{target_name}'")
|
||||
return
|
||||
|
||||
print(f"Source: {source_company.name} (ID: {source_company.id})")
|
||||
print(f"Target: {target_company.name} (ID: {target_company.id})")
|
||||
|
||||
# Fetch all payment methods from the source company
|
||||
methods = env['pos.payment.method'].with_context(active_test=False).search([
|
||||
('company_id', '=', source_company.id)
|
||||
])
|
||||
|
||||
print(f"Found {len(methods)} payment methods in {source_company.name} to clone.")
|
||||
|
||||
count = 0
|
||||
for method in methods:
|
||||
# Check if a method with the same name already exists in target to avoid duplicates
|
||||
existing = env['pos.payment.method'].with_context(active_test=False).search([
|
||||
('name', '=', method.name),
|
||||
('company_id', '=', target_company.id)
|
||||
], limit=1)
|
||||
|
||||
if existing:
|
||||
print(f" -> '{method.name}' already exists in {target_company.name}. Skipping.")
|
||||
continue
|
||||
|
||||
# Clone the method and assign to target company
|
||||
# the .copy() method will duplicate the record. We just override company_id.
|
||||
# Ensure we pass the explicit account mappings to avoid Odoo triggering a duplicate account creation
|
||||
new_method = method.copy({
|
||||
'company_id': target_company.id,
|
||||
'receivable_account_id': method.receivable_account_id.id if method.receivable_account_id else False,
|
||||
'outstanding_account_id': method.outstanding_account_id.id if method.outstanding_account_id else False,
|
||||
'journal_id': method.journal_id.id if method.journal_id else False,
|
||||
})
|
||||
print(f" -> Cloned '{method.name}' (New ID: {new_method.id})")
|
||||
count += 1
|
||||
|
||||
print(f"Committing changes... (Cloned {count} methods)")
|
||||
env.cr.commit()
|
||||
print("Cloning complete!")
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
clone_payment_methods(env)
|
||||
except NameError:
|
||||
print("Please run this script using Odoo shell:")
|
||||
print("Example: ./.venv/bin/python ./odoo/odoo-bin shell -d <DB_NAME> -c odoo.conf < scripts/clone_pos_payment_methods.py")
|
||||
40
diagnose_pos_close.py
Normal file
40
diagnose_pos_close.py
Normal file
@ -0,0 +1,40 @@
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
def diagnose_pos_sessions(env):
|
||||
sessions = env['pos.session'].search([('state', '!=', 'closed')])
|
||||
if not sessions:
|
||||
print("No open POS sessions found.")
|
||||
return
|
||||
|
||||
for session in sessions:
|
||||
print(f"Session: {session.name} | State: {session.state} | Company ID: {session.company_id.id}")
|
||||
|
||||
orders = env['pos.order'].search([('session_id', '=', session.id)])
|
||||
for order in orders:
|
||||
if order.company_id.id != session.company_id.id:
|
||||
print(f" [!] Order {order.name} has Company ID {order.company_id.id} (Expected: {session.company_id.id})")
|
||||
|
||||
for line in order.lines:
|
||||
if line.company_id.id != session.company_id.id:
|
||||
print(f" [!] Order Line {line.id} has Company ID {line.company_id.id}")
|
||||
|
||||
payments = env['pos.payment'].search([('session_id', '=', session.id)])
|
||||
for payment in payments:
|
||||
if payment.company_id.id != session.company_id.id:
|
||||
print(f" [!] Payment {payment.name} has Company ID {payment.company_id.id}")
|
||||
|
||||
if session.move_id and session.move_id.company_id.id != session.company_id.id:
|
||||
print(f" [!] Account Move {session.move_id.name} has Company ID {session.move_id.company_id.id}")
|
||||
|
||||
print(" -> Attempting to close session...")
|
||||
try:
|
||||
# We bypass user domain restrictions for the script
|
||||
session.with_context(allowed_company_ids=[session.company_id.id]).action_pos_session_closing_control()
|
||||
print(f" -> Successfully closed session {session.name}!")
|
||||
except Exception as e:
|
||||
print(f" -> Failed to close session {session.name}:")
|
||||
traceback.print_exc()
|
||||
|
||||
if __name__ == '__main__':
|
||||
diagnose_pos_sessions(env)
|
||||
56
fix_company_journals.py
Normal file
56
fix_company_journals.py
Normal file
@ -0,0 +1,56 @@
|
||||
import sys
|
||||
|
||||
def fix_company_journals(env):
|
||||
company_name = 'Kedai Kipas 58 Tenggilis'
|
||||
company = env['res.company'].search([('name', 'ilike', company_name)], limit=1)
|
||||
|
||||
if not company:
|
||||
print(f"Company {company_name} not found.")
|
||||
return
|
||||
|
||||
print(f"Fixing journals for company: {company.name} (ID: {company.id})")
|
||||
|
||||
# We look for the equivalent journals in Tenggilis
|
||||
# 1. Cash Basis Taxes (Usually code CABA or name 'Cash Basis Taxes')
|
||||
cash_basis_journal = env['account.journal'].search([
|
||||
('company_id', '=', company.id),
|
||||
('code', '=', 'CABA') # Assuming code is CABA. If not, try by name.
|
||||
], limit=1)
|
||||
|
||||
if not cash_basis_journal:
|
||||
cash_basis_journal = env['account.journal'].search([
|
||||
('company_id', '=', company.id),
|
||||
('name', 'ilike', 'Cash Basis')
|
||||
], limit=1)
|
||||
|
||||
vals = {}
|
||||
if cash_basis_journal:
|
||||
print(f"Setting Cash Basis Journal to: {cash_basis_journal.name} (ID: {cash_basis_journal.id})")
|
||||
vals['tax_cash_basis_journal_id'] = cash_basis_journal.id
|
||||
else:
|
||||
print("Could not find a Cash Basis journal for Tenggilis to set. You may need to clear it or create one.")
|
||||
vals['tax_cash_basis_journal_id'] = False # Temporary clear if not found to allow saving
|
||||
|
||||
# 2. account_tax_periodicity_journal_id
|
||||
tax_journal = env['account.journal'].search([
|
||||
('company_id', '=', company.id),
|
||||
('name', 'ilike', 'Kas Kecil Operasional Tenggilis')
|
||||
], limit=1)
|
||||
|
||||
if tax_journal:
|
||||
print(f"Setting Tax Periodicity Journal to: {tax_journal.name} (ID: {tax_journal.id})")
|
||||
vals['account_tax_periodicity_journal_id'] = tax_journal.id
|
||||
else:
|
||||
print("Could not find matching Tax Periodicity journal for Tenggilis. Clearing it to allow saving.")
|
||||
vals['account_tax_periodicity_journal_id'] = False
|
||||
|
||||
company.write(vals)
|
||||
print(f"Fixed {len(vals)} company settings.")
|
||||
env.cr.commit()
|
||||
print("Done!")
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
fix_company_journals(env)
|
||||
except NameError:
|
||||
print("Run via odoo shell")
|
||||
82
fix_mismatched_journals.py
Normal file
82
fix_mismatched_journals.py
Normal file
@ -0,0 +1,82 @@
|
||||
import sys
|
||||
|
||||
def fix_all_mismatched_journals(env):
|
||||
company_name = 'Kedai Kipas 58 Tenggilis'
|
||||
company = env['res.company'].search([('name', 'ilike', company_name)], limit=1)
|
||||
|
||||
if not company:
|
||||
print(f"Company {company_name} not found.")
|
||||
return
|
||||
|
||||
print(f"Checking {company.name} for mismatched journal entries...")
|
||||
|
||||
# Let's forcefully clear ANY journal that doesn't belong to Tenggilis from its configurations
|
||||
# Common journal configuration fields
|
||||
fields_to_check = [
|
||||
'account_tax_periodicity_journal_id',
|
||||
'tax_cash_basis_journal_id',
|
||||
'currency_exdiff_journal_id'
|
||||
# Can add more if needed
|
||||
]
|
||||
|
||||
vals = {}
|
||||
for field in fields_to_check:
|
||||
try:
|
||||
journal_id = getattr(company, field, False)
|
||||
if journal_id and journal_id.company_id and journal_id.company_id.id != company.id:
|
||||
print(f"[x] Mismatched Journal in field '{field}': {journal_id.name} (from {journal_id.company_id.name}) -> Clearing")
|
||||
vals[field] = False
|
||||
except Exception as e:
|
||||
print(f"Error checking {field}: {e}")
|
||||
|
||||
# Also check pos configs for this company
|
||||
pos_configs = env['pos.config'].search([('company_id', '=', company.id)])
|
||||
for config in pos_configs:
|
||||
config_vals = {}
|
||||
if config.journal_id and config.journal_id.company_id and config.journal_id.company_id.id != company.id:
|
||||
print(f"[x] Mismatched Journal in POS Config '{config.name}': {config.journal_id.name} -> Clearing")
|
||||
config_vals['journal_id'] = False
|
||||
|
||||
if config.invoice_journal_id and config.invoice_journal_id.company_id and config.invoice_journal_id.company_id.id != company.id:
|
||||
print(f"[x] Mismatched Invoice Journal in POS Config '{config.name}': {config.invoice_journal_id.name} -> Clearing")
|
||||
config_vals['invoice_journal_id'] = False
|
||||
|
||||
if config_vals:
|
||||
config.write(config_vals)
|
||||
|
||||
if vals:
|
||||
company.write(vals)
|
||||
print(f"Updated {len(vals)} fields on company.")
|
||||
else:
|
||||
print("No mismatched journals found on company settings.")
|
||||
|
||||
print("Finding and fixing pos payment methods with wrong journal...")
|
||||
methods = env['pos.payment.method'].with_context(active_test=False).search([
|
||||
('company_id', '=', company.id),
|
||||
('journal_id.company_id', '!=', company.id),
|
||||
('journal_id', '!=', False)
|
||||
])
|
||||
|
||||
for method in methods:
|
||||
print(f"[x] Fixing payment method '{method.name}' (Journal {method.journal_id.name} is from {method.journal_id.company_id.name}) -> Clearing")
|
||||
method.journal_id = False
|
||||
|
||||
# Check for Bank Accounts (res.partner.bank) linked to the wrong journal
|
||||
banks = env['res.partner.bank'].with_context(active_test=False).search([
|
||||
('company_id', '=', company.id),
|
||||
('journal_id.company_id', '!=', company.id),
|
||||
('journal_id', '!=', False)
|
||||
])
|
||||
for bank in banks:
|
||||
print(f"[x] Fixing bank account '{bank.acc_number}' (Journal {bank.journal_id.name} is from {bank.journal_id.company_id.name}) -> Clearing")
|
||||
bank.journal_id = False
|
||||
|
||||
|
||||
env.cr.commit()
|
||||
print("Done!")
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
fix_all_mismatched_journals(env)
|
||||
except NameError:
|
||||
print("Run via odoo shell")
|
||||
125
migrate_to_branch.py
Normal file
125
migrate_to_branch.py
Normal file
@ -0,0 +1,125 @@
|
||||
import sys
|
||||
|
||||
def migrate_to_branch(env):
|
||||
source_name = 'PT Kipas Lima Delapan'
|
||||
target_name = 'Kedai Kipas 58 Rungkut'
|
||||
|
||||
source_company = env['res.company'].search([('name', '=', source_name)], limit=1)
|
||||
target_company = env['res.company'].search([('name', '=', target_name)], limit=1)
|
||||
|
||||
if not source_company or not target_company:
|
||||
print(f"Could not find one or both companies: '{source_name}', '{target_name}'")
|
||||
return
|
||||
|
||||
print(f"Source Company: {source_company.name} (ID: {source_company.id})")
|
||||
print(f"Target Company: {target_company.name} (ID: {target_company.id})")
|
||||
|
||||
# List of safe transaction and config tables to update.
|
||||
# We avoid global/shared configs like account_account, account_tax, product_template, res_partner, res_users
|
||||
# as these should either remain with the parent company (in Odoo 17 branch setups) or be global.
|
||||
tables_to_update = [
|
||||
# POS Config & Transactions
|
||||
'pos_config',
|
||||
'pos_session',
|
||||
'pos_order',
|
||||
'pos_order_line',
|
||||
'pos_payment',
|
||||
'pos_payment_method',
|
||||
|
||||
# Shop Floor / Inventory
|
||||
'stock_warehouse',
|
||||
'stock_location',
|
||||
'stock_picking_type',
|
||||
'stock_picking',
|
||||
'stock_move',
|
||||
'stock_move_line',
|
||||
'stock_quant',
|
||||
'stock_valuation_layer',
|
||||
'stock_scrap',
|
||||
'stock_inventory',
|
||||
'stock_rule',
|
||||
'stock_route',
|
||||
'stock_putaway_rule',
|
||||
|
||||
# Shop Floor / Manufacturing
|
||||
'mrp_production',
|
||||
'mrp_workorder',
|
||||
'mrp_workcenter',
|
||||
'mrp_routing_workcenter',
|
||||
'mrp_bom',
|
||||
'mrp_bom_line',
|
||||
'mrp_unbuild',
|
||||
'mrp_consumption_warning',
|
||||
|
||||
# Journals and Accounting Transactions
|
||||
'account_journal',
|
||||
'account_move',
|
||||
'account_move_line',
|
||||
'account_payment',
|
||||
'account_bank_statement',
|
||||
'account_bank_statement_line',
|
||||
'account_partial_reconcile',
|
||||
'account_payment_term',
|
||||
|
||||
# Sales and Purchases
|
||||
'sale_order',
|
||||
'sale_order_line',
|
||||
'purchase_order',
|
||||
'purchase_order_line',
|
||||
'purchase_requisition',
|
||||
'purchase_requisition_line',
|
||||
|
||||
# Employees and HR (Optional, remove if employees shouldn't be moved)
|
||||
'hr_employee',
|
||||
'hr_contract',
|
||||
'hr_attendance',
|
||||
'hr_payslip',
|
||||
'hr_expense',
|
||||
'hr_expense_sheet',
|
||||
|
||||
# Products / Pricing
|
||||
'product_pricelist',
|
||||
|
||||
# Properties / Sequences
|
||||
'ir_sequence',
|
||||
]
|
||||
|
||||
# We'll use raw SQL because Odoo ORM will block updates to company_id on posted entries and confirmed orders.
|
||||
total_updated = 0
|
||||
for table in tables_to_update:
|
||||
# Check if table exists and has company_id
|
||||
env.cr.execute("""
|
||||
SELECT column_name
|
||||
FROM information_schema.columns
|
||||
WHERE table_name=%s AND column_name='company_id'
|
||||
""", (table,))
|
||||
|
||||
if env.cr.fetchone():
|
||||
env.cr.execute(f"""
|
||||
UPDATE {table}
|
||||
SET company_id = %s
|
||||
WHERE company_id = %s
|
||||
""", (target_company.id, source_company.id))
|
||||
|
||||
rowcount = env.cr.rowcount
|
||||
if rowcount > 0:
|
||||
print(f"Updated {rowcount} records in table '{table}'")
|
||||
total_updated += rowcount
|
||||
|
||||
# Note on ir_property: it stores things like default receivable/payable accounts
|
||||
# Updating them blindly might break the parent company. If the parent
|
||||
# needs to remain somewhat functional, we probably shouldn't migrate all ir_properties.
|
||||
# But if it's strictly a "move everything to the new shop", we could.
|
||||
# For safety, I've left ir_property out and let standard branch inheritance handle accounts.
|
||||
|
||||
print(f"Committing changes... (Total {total_updated} rows updated)")
|
||||
env.cr.commit()
|
||||
env.invalidate_all()
|
||||
print("Migration complete!")
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
migrate_to_branch(env)
|
||||
except NameError:
|
||||
print("Please run this script using Odoo shell:")
|
||||
print("Example: ./.venv/bin/python ./odoo/odoo-bin shell -d <DB_NAME> -c odoo.conf < scripts/migrate_to_branch.py")
|
||||
361
run_all_migrations.py
Normal file
361
run_all_migrations.py
Normal file
@ -0,0 +1,361 @@
|
||||
import sys
|
||||
|
||||
# ==========================================
|
||||
# 1. MIGRATE TO BRANCH
|
||||
# ==========================================
|
||||
def migrate_to_branch(env):
|
||||
print("\n==========================================")
|
||||
print("1. RUNNING CORE DATA MIGRATION")
|
||||
print("==========================================")
|
||||
source_name = 'PT Kipas Lima Delapan'
|
||||
target_name = 'Kedai Kipas 58 Rungkut'
|
||||
|
||||
source_company = env['res.company'].search([('name', '=', source_name)], limit=1)
|
||||
target_company = env['res.company'].search([('name', '=', target_name)], limit=1)
|
||||
|
||||
if not source_company or not target_company:
|
||||
print(f"Could not find one or both companies: '{source_name}', '{target_name}'")
|
||||
return
|
||||
|
||||
print(f"Source Company: {source_company.name} (ID: {source_company.id})")
|
||||
print(f"Target Company: {target_company.name} (ID: {target_company.id})")
|
||||
|
||||
tables_to_update = [
|
||||
'pos_config', 'pos_session', 'pos_order', 'pos_order_line', 'pos_payment', 'pos_payment_method',
|
||||
'stock_warehouse', 'stock_location', 'stock_picking_type', 'stock_picking', 'stock_move', 'stock_move_line',
|
||||
'stock_quant', 'stock_valuation_layer', 'stock_scrap', 'stock_inventory', 'stock_rule', 'stock_route', 'stock_putaway_rule',
|
||||
'mrp_production', 'mrp_workorder', 'mrp_workcenter', 'mrp_routing_workcenter', 'mrp_bom', 'mrp_bom_line', 'mrp_unbuild', 'mrp_consumption_warning',
|
||||
'account_journal', 'account_move', 'account_move_line', 'account_payment', 'account_bank_statement', 'account_bank_statement_line', 'account_partial_reconcile', 'account_payment_term',
|
||||
'sale_order', 'sale_order_line', 'purchase_order', 'purchase_order_line', 'purchase_requisition', 'purchase_requisition_line',
|
||||
'hr_employee', 'hr_contract', 'hr_attendance', 'hr_payslip', 'hr_expense', 'hr_expense_sheet',
|
||||
'product_pricelist', 'ir_sequence'
|
||||
]
|
||||
|
||||
total_updated = 0
|
||||
for table in tables_to_update:
|
||||
env.cr.execute("""
|
||||
SELECT column_name
|
||||
FROM information_schema.columns
|
||||
WHERE table_name=%s AND column_name='company_id'
|
||||
""", (table,))
|
||||
|
||||
if env.cr.fetchone():
|
||||
env.cr.execute(f"""
|
||||
UPDATE {table}
|
||||
SET company_id = %s
|
||||
WHERE company_id = %s
|
||||
""", (target_company.id, source_company.id))
|
||||
rowcount = env.cr.rowcount
|
||||
if rowcount > 0:
|
||||
print(f"Updated {rowcount} records in table '{table}'")
|
||||
total_updated += rowcount
|
||||
|
||||
print(f"Committing changes... (Total {total_updated} rows updated)")
|
||||
env.cr.commit()
|
||||
env.invalidate_all()
|
||||
print("Migration complete!")
|
||||
|
||||
|
||||
# ==========================================
|
||||
# 2. CLONE ACCOUNTING JOURNALS
|
||||
# ==========================================
|
||||
def clone_journals(env):
|
||||
print("\n==========================================")
|
||||
print("2. CLONING ACCOUNTING JOURNALS")
|
||||
print("==========================================")
|
||||
source_name = 'Kedai Kipas 58 Rungkut'
|
||||
target_name = 'Kedai Kipas 58 Tenggilis'
|
||||
|
||||
source_company = env['res.company'].search([('name', 'ilike', source_name)], limit=1)
|
||||
target_company = env['res.company'].search([('name', 'ilike', target_name)], limit=1)
|
||||
|
||||
if not source_company or not target_company:
|
||||
print(f"Could not find one or both companies: '{source_name}', '{target_name}'")
|
||||
return
|
||||
|
||||
journals = env['account.journal'].with_context(active_test=False).search([
|
||||
('company_id', '=', source_company.id)
|
||||
])
|
||||
|
||||
print(f"Found {len(journals)} journals in {source_company.name} to clone.")
|
||||
|
||||
count = 0
|
||||
for journal in journals:
|
||||
existing = env['account.journal'].with_context(active_test=False).search([
|
||||
('code', '=', journal.code),
|
||||
('company_id', '=', target_company.id)
|
||||
], limit=1)
|
||||
|
||||
if existing:
|
||||
print(f" -> '{journal.name}' (Code: {journal.code}) already exists in {target_company.name}. Skipping.")
|
||||
continue
|
||||
|
||||
try:
|
||||
with env.cr.savepoint():
|
||||
copy_defaults = {
|
||||
'company_id': target_company.id,
|
||||
'default_account_id': journal.default_account_id.id if journal.default_account_id else False,
|
||||
'suspense_account_id': journal.suspense_account_id.id if journal.suspense_account_id else False,
|
||||
'profit_account_id': journal.profit_account_id.id if journal.profit_account_id else False,
|
||||
'loss_account_id': journal.loss_account_id.id if journal.loss_account_id else False,
|
||||
}
|
||||
new_journal = journal.copy(copy_defaults)
|
||||
new_journal.write({
|
||||
'name': journal.name,
|
||||
'code': journal.code,
|
||||
})
|
||||
print(f" -> Cloned '{journal.name}' [{journal.code}] (New ID: {new_journal.id})")
|
||||
count += 1
|
||||
except Exception as e:
|
||||
print(f" -> Failed to clone '{journal.name}' [{journal.code}]: {e}")
|
||||
|
||||
print(f"Committing changes... (Cloned {count} journals)")
|
||||
env.cr.commit()
|
||||
|
||||
|
||||
# ==========================================
|
||||
# 3. CLONE POS PAYMENT METHODS
|
||||
# ==========================================
|
||||
def clone_payment_methods(env):
|
||||
print("\n==========================================")
|
||||
print("3. CLONING POS PAYMENT METHODS")
|
||||
print("==========================================")
|
||||
source_name = 'Kedai Kipas 58 Rungkut'
|
||||
target_name = 'Kedai Kipas 58 Tenggilis'
|
||||
|
||||
source_company = env['res.company'].search([('name', 'ilike', source_name)], limit=1)
|
||||
target_company = env['res.company'].search([('name', 'ilike', target_name)], limit=1)
|
||||
|
||||
methods = env['pos.payment.method'].with_context(active_test=False).search([
|
||||
('company_id', '=', source_company.id)
|
||||
])
|
||||
print(f"Found {len(methods)} payment methods to clone.")
|
||||
|
||||
count = 0
|
||||
for method in methods:
|
||||
existing = env['pos.payment.method'].with_context(active_test=False).search([
|
||||
('name', '=', method.name),
|
||||
('company_id', '=', target_company.id)
|
||||
], limit=1)
|
||||
|
||||
if existing:
|
||||
print(f" -> '{method.name}' already exists in target. Skipping.")
|
||||
continue
|
||||
|
||||
new_method = method.copy({
|
||||
'company_id': target_company.id,
|
||||
'receivable_account_id': method.receivable_account_id.id if method.receivable_account_id else False,
|
||||
'outstanding_account_id': method.outstanding_account_id.id if method.outstanding_account_id else False,
|
||||
'journal_id': method.journal_id.id if method.journal_id else False,
|
||||
})
|
||||
print(f" -> Cloned '{method.name}' (New ID: {new_method.id})")
|
||||
count += 1
|
||||
|
||||
print(f"Committing changes... (Cloned {count} methods)")
|
||||
env.cr.commit()
|
||||
|
||||
|
||||
# ==========================================
|
||||
# 4. CLONE CASH ROUNDING & PRICELISTS
|
||||
# ==========================================
|
||||
def clone_rounding_and_pricelist(env):
|
||||
print("\n==========================================")
|
||||
print("4. CLONING CASH ROUNDING & PRICELISTS")
|
||||
print("==========================================")
|
||||
source_name = 'Kedai Kipas 58 Rungkut'
|
||||
target_name = 'Kedai Kipas 58 Tenggilis'
|
||||
|
||||
source_company = env['res.company'].search([('name', 'ilike', source_name)], limit=1)
|
||||
target_company = env['res.company'].search([('name', 'ilike', target_name)], limit=1)
|
||||
|
||||
print("--- Applying Cash Rounding Setup from Rungkut to Tenggilis POS ---")
|
||||
source_configs = env['pos.config'].with_context(active_test=False).search([
|
||||
('company_id', '=', source_company.id),
|
||||
('cash_rounding', '=', True)
|
||||
], limit=1)
|
||||
|
||||
target_configs = env['pos.config'].with_context(active_test=False).search([
|
||||
('company_id', '=', target_company.id)
|
||||
])
|
||||
|
||||
if source_configs and target_configs:
|
||||
template_config = source_configs[0]
|
||||
for t_config in target_configs:
|
||||
t_config.write({
|
||||
'cash_rounding': template_config.cash_rounding,
|
||||
'rounding_method': template_config.rounding_method.id if template_config.rounding_method else False,
|
||||
'only_round_cash_method': template_config.only_round_cash_method,
|
||||
})
|
||||
print(f" -> Applied Cash Rounding setup to POS '{t_config.name}'")
|
||||
|
||||
print("\n--- Cloning Pricelists ---")
|
||||
pricelists = env['product.pricelist'].with_context(active_test=False).search([
|
||||
('company_id', '=', source_company.id)
|
||||
])
|
||||
|
||||
count_pricelist = 0
|
||||
for pricelist in pricelists:
|
||||
existing = env['product.pricelist'].with_context(active_test=False).search([
|
||||
('name', '=', pricelist.name),
|
||||
('company_id', '=', target_company.id)
|
||||
], limit=1)
|
||||
|
||||
if existing:
|
||||
print(f" -> '{pricelist.name}' already exists. Skipping.")
|
||||
continue
|
||||
|
||||
try:
|
||||
with env.cr.savepoint():
|
||||
new_pricelist = pricelist.copy({
|
||||
'company_id': target_company.id,
|
||||
'website_id': False,
|
||||
})
|
||||
new_pricelist.write({'name': pricelist.name})
|
||||
print(f" -> Cloned Pricelist '{pricelist.name}'")
|
||||
count_pricelist += 1
|
||||
except Exception as e:
|
||||
print(f" -> Failed to clone Pricelist: {e}")
|
||||
|
||||
if source_configs and target_configs:
|
||||
print("\n--- Updating POS Pricelist configuration ---")
|
||||
for s_config, t_config in zip(source_configs, target_configs):
|
||||
if s_config.pricelist_id:
|
||||
matching_pl = env['product.pricelist'].with_context(active_test=False).search([
|
||||
('name', '=', s_config.pricelist_id.name),
|
||||
('company_id', '=', target_company.id)
|
||||
], limit=1)
|
||||
if matching_pl:
|
||||
t_config.write({'pricelist_id': matching_pl.id})
|
||||
print(f" -> Set Pricelist '{matching_pl.name}' on POS '{t_config.name}'")
|
||||
|
||||
if s_config.available_pricelist_ids:
|
||||
mapped_ids = []
|
||||
for pl in s_config.available_pricelist_ids:
|
||||
match = env['product.pricelist'].with_context(active_test=False).search([
|
||||
('name', '=', pl.name),
|
||||
('company_id', '=', target_company.id)
|
||||
], limit=1)
|
||||
if match:
|
||||
mapped_ids.append(match.id)
|
||||
if mapped_ids:
|
||||
t_config.write({'available_pricelist_ids': [(6, 0, mapped_ids)]})
|
||||
print(f" -> Set Available Pricelists on POS '{t_config.name}'")
|
||||
|
||||
print(f"Committing changes... (Cloned setup, {count_pricelist} pricelists)")
|
||||
env.cr.commit()
|
||||
|
||||
|
||||
# ==========================================
|
||||
# 5. FIX COMPANY JOURNALS
|
||||
# ==========================================
|
||||
def fix_company_journals(env):
|
||||
print("\n==========================================")
|
||||
print("5. FIXING RES.COMPANY JOURNALS")
|
||||
print("==========================================")
|
||||
company_name = 'Kedai Kipas 58 Tenggilis'
|
||||
company = env['res.company'].search([('name', 'ilike', company_name)], limit=1)
|
||||
|
||||
if not company:
|
||||
return
|
||||
|
||||
cash_basis_journal = env['account.journal'].search([
|
||||
('company_id', '=', company.id),
|
||||
('code', '=', 'CABA')
|
||||
], limit=1)
|
||||
|
||||
if not cash_basis_journal:
|
||||
cash_basis_journal = env['account.journal'].search([
|
||||
('company_id', '=', company.id),
|
||||
('name', 'ilike', 'Cash Basis')
|
||||
], limit=1)
|
||||
|
||||
vals = {}
|
||||
if cash_basis_journal:
|
||||
vals['tax_cash_basis_journal_id'] = cash_basis_journal.id
|
||||
else:
|
||||
vals['tax_cash_basis_journal_id'] = False
|
||||
|
||||
tax_journal = env['account.journal'].search([
|
||||
('company_id', '=', company.id),
|
||||
('name', 'ilike', 'Kas Kecil Operasional Tenggilis')
|
||||
], limit=1)
|
||||
|
||||
if tax_journal:
|
||||
vals['account_tax_periodicity_journal_id'] = tax_journal.id
|
||||
else:
|
||||
vals['account_tax_periodicity_journal_id'] = False
|
||||
|
||||
company.write(vals)
|
||||
print(f"Fixed company journal settings.")
|
||||
env.cr.commit()
|
||||
|
||||
|
||||
# ==========================================
|
||||
# 6. FIX ALL MISMATCHED JOURNALS
|
||||
# ==========================================
|
||||
def fix_all_mismatched_journals(env):
|
||||
print("\n==========================================")
|
||||
print("6. FIXING ALL MISMATCHED JOURNALS")
|
||||
print("==========================================")
|
||||
company_name = 'Kedai Kipas 58 Tenggilis'
|
||||
company = env['res.company'].search([('name', 'ilike', company_name)], limit=1)
|
||||
|
||||
if not company:
|
||||
return
|
||||
|
||||
fields_to_check = ['account_tax_periodicity_journal_id', 'tax_cash_basis_journal_id', 'currency_exdiff_journal_id']
|
||||
vals = {}
|
||||
for field in fields_to_check:
|
||||
try:
|
||||
journal_id = getattr(company, field, False)
|
||||
if journal_id and journal_id.company_id and journal_id.company_id.id != company.id:
|
||||
vals[field] = False
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
pos_configs = env['pos.config'].search([('company_id', '=', company.id)])
|
||||
for config in pos_configs:
|
||||
config_vals = {}
|
||||
if config.journal_id and config.journal_id.company_id and config.journal_id.company_id.id != company.id:
|
||||
config_vals['journal_id'] = False
|
||||
if config.invoice_journal_id and config.invoice_journal_id.company_id and config.invoice_journal_id.company_id.id != company.id:
|
||||
config_vals['invoice_journal_id'] = False
|
||||
if config_vals:
|
||||
config.write(config_vals)
|
||||
|
||||
if vals:
|
||||
company.write(vals)
|
||||
|
||||
methods = env['pos.payment.method'].with_context(active_test=False).search([
|
||||
('company_id', '=', company.id),
|
||||
('journal_id.company_id', '!=', company.id),
|
||||
('journal_id', '!=', False)
|
||||
])
|
||||
for method in methods:
|
||||
method.journal_id = False
|
||||
|
||||
banks = env['res.partner.bank'].with_context(active_test=False).search([
|
||||
('company_id', '=', company.id),
|
||||
('journal_id.company_id', '!=', company.id),
|
||||
('journal_id', '!=', False)
|
||||
])
|
||||
for bank in banks:
|
||||
bank.journal_id = False
|
||||
|
||||
print("Fixed leftover mismatched journals.")
|
||||
env.cr.commit()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
print("Starting full migration and cloning process...")
|
||||
migrate_to_branch(env)
|
||||
clone_journals(env)
|
||||
clone_payment_methods(env)
|
||||
clone_rounding_and_pricelist(env)
|
||||
fix_company_journals(env)
|
||||
fix_all_mismatched_journals(env)
|
||||
print("\n*** ALL MIGRATIONS COMPLETED SUCCESSFULLY! ***")
|
||||
except NameError:
|
||||
print("Please run this script using Odoo shell:")
|
||||
print("Example: ./.venv/bin/python ./odoo/odoo-bin shell -d <DB_NAME> -c odoo.conf < scripts/run_all_migrations.py")
|
||||
Loading…
Reference in New Issue
Block a user