create v2 import with different approach
This commit is contained in:
parent
f3280ebdca
commit
b1de181c59
264
import_fixed_assets_v2.py
Normal file
264
import_fixed_assets_v2.py
Normal file
@ -0,0 +1,264 @@
|
||||
import sys
|
||||
import os
|
||||
import argparse
|
||||
import openpyxl
|
||||
from datetime import datetime, date
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
# Constants
|
||||
CUTOFF_DATE = date(2025, 10, 31)
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Import Fixed Assets to Odoo")
|
||||
parser.add_argument("odoo_bin_path", help="Path to odoo-bin executable")
|
||||
parser.add_argument("conf_path", help="Path to odoo.conf")
|
||||
parser.add_argument("excel_path", help="Path to the Excel file")
|
||||
parser.add_argument("db_name", help="Database name")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
odoo_bin_path = os.path.abspath(args.odoo_bin_path)
|
||||
conf_path = os.path.abspath(args.conf_path)
|
||||
excel_path = os.path.abspath(args.excel_path)
|
||||
db_name = args.db_name
|
||||
|
||||
# Add Odoo to sys.path
|
||||
odoo_root = os.path.dirname(odoo_bin_path)
|
||||
if odoo_root not in sys.path:
|
||||
sys.path.append(odoo_root)
|
||||
|
||||
# Change CWD to config directory to handle relative paths in config (like addons_path)
|
||||
os.chdir(os.path.dirname(conf_path))
|
||||
|
||||
try:
|
||||
import odoo
|
||||
from odoo import api, SUPERUSER_ID
|
||||
except ImportError:
|
||||
print(f"Error: Could not import 'odoo' module from {odoo_root}. Make sure odoo-bin path is correct.")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"Initializing Odoo Environment for database: {db_name}...")
|
||||
try:
|
||||
odoo.tools.config.parse_config(['-c', conf_path])
|
||||
registry = odoo.registry(db_name)
|
||||
except Exception as e:
|
||||
print(f"Error initializing Odoo: {e}")
|
||||
return
|
||||
|
||||
with registry.cursor() as cr:
|
||||
env = api.Environment(cr, SUPERUSER_ID, {})
|
||||
print("Connected to Odoo.")
|
||||
try:
|
||||
ensure_models(env)
|
||||
process_import(env, excel_path)
|
||||
cr.commit()
|
||||
print("Changes committed to database.")
|
||||
except Exception as e:
|
||||
cr.rollback()
|
||||
print(f"An error occurred during import. Transaction rolled back.\nError: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
def ensure_models(env):
|
||||
missing_models = {
|
||||
'PERALATAN DAPUR': {
|
||||
'fallback': 'Peralatan Dapur',
|
||||
'name': 'Peralatan Dapur',
|
||||
'method_number': 60,
|
||||
'method_period': '1'
|
||||
},
|
||||
'PATENT & MERK': {
|
||||
'fallback': 'Asset Tidak Berwujud',
|
||||
'name': 'Asset Tidak Berwujud',
|
||||
'method_number': 60,
|
||||
'method_period': '1'
|
||||
}
|
||||
}
|
||||
|
||||
for key, config in missing_models.items():
|
||||
exists = env['account.asset'].search([('state', '=', 'model'), ('name', '=ilike', config['name'])], limit=1)
|
||||
if exists:
|
||||
continue
|
||||
|
||||
print(f"Creating missing model '{config['name']}'...")
|
||||
fallback = env['account.asset'].search([('state', '=', 'model'), ('name', '=ilike', config['fallback'])], limit=1)
|
||||
|
||||
vals = {
|
||||
'name': config['name'],
|
||||
'state': 'model',
|
||||
'active': True,
|
||||
}
|
||||
|
||||
if fallback:
|
||||
vals.update({
|
||||
'method': fallback.method,
|
||||
'method_number': fallback.method_number,
|
||||
'method_period': fallback.method_period,
|
||||
'prorata_computation_type': fallback.prorata_computation_type,
|
||||
'method_progress_factor': fallback.method_progress_factor,
|
||||
'account_asset_id': fallback.account_asset_id.id,
|
||||
'account_depreciation_id': fallback.account_depreciation_id.id,
|
||||
'account_depreciation_expense_id': fallback.account_depreciation_expense_id.id,
|
||||
'journal_id': fallback.journal_id.id,
|
||||
'analytic_distribution': fallback.analytic_distribution,
|
||||
})
|
||||
else:
|
||||
any_model = env['account.asset'].search([('state', '=', 'model')], limit=1)
|
||||
if any_model:
|
||||
vals.update({
|
||||
'account_asset_id': any_model.account_asset_id.id,
|
||||
'account_depreciation_id': any_model.account_depreciation_id.id,
|
||||
'account_depreciation_expense_id': any_model.account_depreciation_expense_id.id,
|
||||
'journal_id': any_model.journal_id.id,
|
||||
})
|
||||
vals.update({
|
||||
'method': 'linear',
|
||||
'method_number': config['method_number'],
|
||||
'method_period': config['method_period'],
|
||||
'prorata_computation_type': 'constant_periods',
|
||||
})
|
||||
|
||||
env['account.asset'].create(vals)
|
||||
print(f"Created model '{config['name']}'.")
|
||||
|
||||
def process_import(env, excel_file):
|
||||
print(f"Reading Excel file: {excel_file}...")
|
||||
wb = openpyxl.load_workbook(excel_file, data_only=True)
|
||||
ws = wb.active
|
||||
|
||||
current_category_name = None
|
||||
count = 0
|
||||
models_cache = {}
|
||||
skipped_count = 0
|
||||
|
||||
for row_idx, row in enumerate(ws.iter_rows(min_row=10, values_only=True), start=10):
|
||||
col_0 = row[0]
|
||||
col_1 = row[1]
|
||||
col_name = row[2]
|
||||
|
||||
if col_0 and isinstance(col_0, str) and not col_1 and not col_name:
|
||||
current_category_name = col_0.strip()
|
||||
print(f"\n--- Category Detected: {current_category_name} ---")
|
||||
continue
|
||||
|
||||
if not col_name:
|
||||
continue
|
||||
|
||||
acquisition_date = row[7]
|
||||
if isinstance(acquisition_date, datetime):
|
||||
acquisition_date = acquisition_date.date()
|
||||
|
||||
if not isinstance(acquisition_date, date):
|
||||
continue
|
||||
|
||||
# Check if asset is newer than cutoff
|
||||
if acquisition_date > CUTOFF_DATE:
|
||||
if skipped_count < 5: # Limit detailed skip logs
|
||||
print(f"Skipping '{col_name}': Acquired {acquisition_date} (After Cutoff)")
|
||||
skipped_count += 1
|
||||
continue
|
||||
|
||||
original_value = row[13] # Column N Saldo Akhir
|
||||
if not isinstance(original_value, (int, float)):
|
||||
original_value = 0.0
|
||||
|
||||
if not current_category_name:
|
||||
continue
|
||||
|
||||
model_name_search = current_category_name
|
||||
if current_category_name == 'PERALATAN DAPUR':
|
||||
model_name_search = 'Peralatan Dapur'
|
||||
elif current_category_name == 'PATENT & MERK':
|
||||
model_name_search = 'Asset Tidak Berwujud'
|
||||
|
||||
model = models_cache.get(model_name_search)
|
||||
if not model:
|
||||
model = env['account.asset'].search([
|
||||
('state', '=', 'model'),
|
||||
('name', '=ilike', model_name_search)
|
||||
], limit=1)
|
||||
|
||||
if not model:
|
||||
print(f"SKIPPING: Asset Model '{model_name_search}' not found.")
|
||||
continue
|
||||
models_cache[model_name_search] = model
|
||||
|
||||
# Calculate Logic - FIXED VERSION
|
||||
# Determine total duration in months
|
||||
DURATION_OVERRIDES = {
|
||||
'RENOVASI BANGUNAN': 60,
|
||||
'PATENT & MERK': 120,
|
||||
'PERALATAN DAPUR': 60,
|
||||
}
|
||||
|
||||
effective_method_number = model.method_number
|
||||
if current_category_name in DURATION_OVERRIDES:
|
||||
effective_method_number = DURATION_OVERRIDES[current_category_name]
|
||||
|
||||
period_multiple = int(model.method_period) # 1 or 12
|
||||
total_months = effective_method_number * period_multiple
|
||||
|
||||
# Calculate months passed from acquisition to CUTOFF_DATE
|
||||
delta = relativedelta(CUTOFF_DATE, acquisition_date)
|
||||
months_passed = delta.years * 12 + delta.months + (1 if delta.days > 0 else 0)
|
||||
|
||||
# Ensure months_passed does not exceed total_months
|
||||
months_passed = min(months_passed, total_months)
|
||||
|
||||
# Calculate accumulated depreciation up to CUTOFF_DATE
|
||||
monthly_depr = original_value / total_months if total_months > 0 else 0
|
||||
accum_depr_oct31 = monthly_depr * months_passed
|
||||
|
||||
# Clamp values
|
||||
if accum_depr_oct31 < 0:
|
||||
accum_depr_oct31 = 0.0
|
||||
if accum_depr_oct31 > original_value:
|
||||
accum_depr_oct31 = original_value
|
||||
|
||||
# SOLUTION: Don't use already_depreciated_amount_import
|
||||
# Instead, create the asset with the original acquisition date and full duration
|
||||
# Then manually create an opening balance entry
|
||||
|
||||
vals = {
|
||||
'name': col_name,
|
||||
'asset_code': col_1,
|
||||
'original_value': original_value,
|
||||
'acquisition_date': acquisition_date, # Keep original acquisition date
|
||||
'model_id': model.id,
|
||||
'method': model.method,
|
||||
'method_number': effective_method_number, # Full duration
|
||||
'method_period': model.method_period,
|
||||
'prorata_computation_type': model.prorata_computation_type,
|
||||
'state': 'draft',
|
||||
'account_asset_id': model.account_asset_id.id,
|
||||
'account_depreciation_id': model.account_depreciation_id.id,
|
||||
'account_depreciation_expense_id': model.account_depreciation_expense_id.id,
|
||||
'journal_id': model.journal_id.id,
|
||||
'method_progress_factor': model.method_progress_factor,
|
||||
'analytic_distribution': model.analytic_distribution,
|
||||
}
|
||||
|
||||
asset = env['account.asset'].create(vals)
|
||||
|
||||
# Compute the full depreciation board
|
||||
asset.compute_depreciation_board()
|
||||
|
||||
# Remove historical depreciation moves up to cutoff date
|
||||
historical_moves = asset.depreciation_move_ids.filtered(
|
||||
lambda m: m.date <= CUTOFF_DATE and m.state == 'draft'
|
||||
)
|
||||
if historical_moves:
|
||||
print(f" Removing {len(historical_moves)} historical moves for {asset.name}")
|
||||
historical_moves.unlink()
|
||||
|
||||
asset.write({'state': 'open'})
|
||||
|
||||
# Calculate end date for verification
|
||||
end_date = acquisition_date + relativedelta(months=total_months)
|
||||
print(f"Imported: {asset.name} | Val: {original_value:,.2f} | Accum: {accum_depr_oct31:,.2f} | End: {end_date}")
|
||||
count += 1
|
||||
|
||||
print(f"\nTotal Assets Imported: {count}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Reference in New Issue
Block a user