469 lines
19 KiB
Python
469 lines
19 KiB
Python
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', 'account_asset',
|
|
'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()
|
|
|
|
|
|
# ==========================================
|
|
# 7. CLONE EXTRA DATA (BOM, CATEGORY COA, PM ACCOUNTS)
|
|
# ==========================================
|
|
def clone_extra_data(env):
|
|
print("\n==========================================")
|
|
print("7. CLONING BOMs, CATEGORY COA & PM ACCOUNTS")
|
|
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:
|
|
return
|
|
|
|
print(f"--- 1. Cloning Product BOMs ---")
|
|
boms = env['mrp.bom'].search([('company_id', '=', source_company.id)])
|
|
count_bom = 0
|
|
for bom in boms:
|
|
existing = env['mrp.bom'].search([
|
|
('product_tmpl_id', '=', bom.product_tmpl_id.id),
|
|
('company_id', '=', target_company.id)
|
|
], limit=1)
|
|
|
|
if existing:
|
|
continue
|
|
|
|
try:
|
|
with env.cr.savepoint():
|
|
bom.copy({'company_id': target_company.id})
|
|
count_bom += 1
|
|
except Exception:
|
|
pass
|
|
|
|
print(f" -> Cloned {count_bom} BOMs")
|
|
|
|
print(f"\n--- 2. Copying Product Category COA Setup ---")
|
|
categories = env['product.category'].with_company(source_company.id).search([])
|
|
count_categ = 0
|
|
for categ in categories:
|
|
income_acc = categ.property_account_income_categ_id
|
|
expense_acc = categ.property_account_expense_categ_id
|
|
stock_val_acc = getattr(categ, 'property_stock_valuation_account_id', False)
|
|
stock_in_acc = getattr(categ, 'property_stock_account_input_categ_id', False)
|
|
stock_out_acc = getattr(categ, 'property_stock_account_output_categ_id', False)
|
|
stock_journal = getattr(categ, 'property_stock_journal', False)
|
|
|
|
vals = {}
|
|
if income_acc: vals['property_account_income_categ_id'] = income_acc.id
|
|
if expense_acc: vals['property_account_expense_categ_id'] = expense_acc.id
|
|
if stock_val_acc: vals['property_stock_valuation_account_id'] = stock_val_acc.id
|
|
if stock_in_acc: vals['property_stock_account_input_categ_id'] = stock_in_acc.id
|
|
if stock_out_acc: vals['property_stock_account_output_categ_id'] = stock_out_acc.id
|
|
|
|
if stock_journal:
|
|
if stock_journal.company_id.id == source_company.id:
|
|
t_journal = env['account.journal'].search([
|
|
('name', '=', stock_journal.name),
|
|
('company_id', '=', target_company.id)
|
|
], limit=1)
|
|
if t_journal: vals['property_stock_journal'] = t_journal.id
|
|
else:
|
|
vals['property_stock_journal'] = stock_journal.id
|
|
|
|
if vals:
|
|
categ.with_company(target_company.id).write(vals)
|
|
count_categ += 1
|
|
print(f" -> Copied Accounting properties for {count_categ} product categories.")
|
|
|
|
print(f"\n--- 3. Fixing POS Payment Methods COA and Journals ---")
|
|
methods = env['pos.payment.method'].search([('company_id', '=', target_company.id)])
|
|
count_pm = 0
|
|
for method in methods:
|
|
orig_method = env['pos.payment.method'].search([
|
|
('name', '=', method.name),
|
|
('company_id', '=', source_company.id)
|
|
], limit=1)
|
|
|
|
if not orig_method:
|
|
continue
|
|
|
|
vals = {}
|
|
orig_journal = orig_method.journal_id
|
|
if orig_journal:
|
|
if orig_journal.company_id.id == source_company.id:
|
|
t_j = env['account.journal'].search([
|
|
('name', '=', orig_journal.name),
|
|
('company_id', '=', target_company.id)
|
|
], limit=1)
|
|
vals['journal_id'] = t_j.id if t_j else False
|
|
else:
|
|
vals['journal_id'] = orig_journal.id
|
|
|
|
if hasattr(orig_method, 'income_account_id') and orig_method.income_account_id:
|
|
vals['income_account_id'] = orig_method.income_account_id.id
|
|
if hasattr(orig_method, 'discount_account_id') and orig_method.discount_account_id:
|
|
vals['discount_account_id'] = orig_method.discount_account_id.id
|
|
|
|
if vals:
|
|
method.write(vals)
|
|
count_pm += 1
|
|
print(f" -> Re-linked Journal & Accounts for {count_pm} Payment Methods")
|
|
|
|
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)
|
|
clone_extra_data(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")
|