333 lines
10 KiB
Python
333 lines
10 KiB
Python
# helpers/migration_mapping.py
|
|
|
|
from datetime import date
|
|
import psycopg2
|
|
from psycopg2.extras import execute_values
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
class MigrationMapper:
|
|
"""Handle migration mapping records between source system and Tryton"""
|
|
|
|
def __init__(self, db_config):
|
|
"""
|
|
Initialize with database configuration
|
|
|
|
Args:
|
|
db_config: dict with keys: host, port, database, user, password
|
|
"""
|
|
self.db_config = db_config
|
|
self.connection = None
|
|
|
|
def __enter__(self):
|
|
"""Context manager entry - establish database connection"""
|
|
self.connection = psycopg2.connect(**self.db_config)
|
|
return self
|
|
|
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
"""Context manager exit - close database connection"""
|
|
if self.connection:
|
|
if exc_type is None:
|
|
self.connection.commit()
|
|
else:
|
|
self.connection.rollback()
|
|
self.connection.close()
|
|
|
|
def save_mapping(self, object_type, source_id, tryton_model, tryton_id, recon_key):
|
|
"""
|
|
Save a single migration mapping record
|
|
|
|
Args:
|
|
object_type: Type of object being migrated (e.g., 'sale_contract')
|
|
source_id: ID from source system
|
|
tryton_model: Tryton model name (e.g., 'sale.sale')
|
|
tryton_id: Tryton record ID
|
|
recon_key: Reconciliation key for matching (e.g., contract number)
|
|
"""
|
|
mappings = [{
|
|
'object_type': object_type,
|
|
'source_id': source_id,
|
|
'tryton_model': tryton_model,
|
|
'tryton_id': tryton_id,
|
|
'recon_key': recon_key
|
|
}]
|
|
self.save_mappings_batch(mappings)
|
|
|
|
def save_mappings_batch(self, mappings):
|
|
"""
|
|
Save multiple migration mapping records in batch
|
|
|
|
Args:
|
|
mappings: List of dicts with keys:
|
|
- object_type: Type of object being migrated
|
|
- source_id: ID from source system
|
|
- tryton_model: Tryton model name
|
|
- tryton_id: Tryton record ID
|
|
- recon_key: Reconciliation key for matching
|
|
"""
|
|
if not mappings:
|
|
logger.warning("No mappings to save")
|
|
return
|
|
|
|
cursor = self.connection.cursor()
|
|
|
|
# Prepare data for batch insert
|
|
values = [
|
|
(
|
|
[mapping['object_type']], # Array with single element
|
|
[mapping['source_id']], # Array with single element
|
|
[mapping['tryton_model']], # Array with single element
|
|
mapping['tryton_id'],
|
|
[mapping['recon_key']], # Array with single element
|
|
date.today()
|
|
)
|
|
for mapping in mappings
|
|
]
|
|
|
|
try:
|
|
# Use execute_values for efficient batch insert
|
|
# ON CONFLICT DO NOTHING prevents duplicates
|
|
execute_values(
|
|
cursor,
|
|
"""
|
|
INSERT INTO public.os_migration_mapping
|
|
(object_type, source_id, tryton_model, tryton_id, recon_key, write_date)
|
|
VALUES %s
|
|
ON CONFLICT DO NOTHING
|
|
""",
|
|
values,
|
|
template="(%s, %s, %s, %s, %s, %s)"
|
|
)
|
|
|
|
logger.info(f"Saved {len(mappings)} migration mapping records")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error saving migration mappings: {e}")
|
|
raise
|
|
finally:
|
|
cursor.close()
|
|
|
|
def get_tryton_id(self, object_type, source_id):
|
|
"""
|
|
Retrieve Tryton ID for a given source system ID
|
|
|
|
Args:
|
|
object_type: Type of object
|
|
source_id: ID from source system
|
|
|
|
Returns:
|
|
int: Tryton ID or None if not found
|
|
"""
|
|
cursor = self.connection.cursor()
|
|
|
|
try:
|
|
cursor.execute(
|
|
"""
|
|
SELECT tryton_id
|
|
FROM public.os_migration_mapping
|
|
WHERE %s = ANY(object_type)
|
|
AND %s = ANY(source_id)
|
|
ORDER BY write_date DESC
|
|
LIMIT 1
|
|
""",
|
|
(object_type, source_id)
|
|
)
|
|
|
|
result = cursor.fetchone()
|
|
return result[0] if result else None
|
|
|
|
finally:
|
|
cursor.close()
|
|
|
|
def get_source_id(self, object_type, tryton_id):
|
|
"""
|
|
Retrieve source system ID for a given Tryton ID
|
|
|
|
Args:
|
|
object_type: Type of object
|
|
tryton_id: Tryton record ID
|
|
|
|
Returns:
|
|
str: Source ID or None if not found
|
|
"""
|
|
cursor = self.connection.cursor()
|
|
|
|
try:
|
|
cursor.execute(
|
|
"""
|
|
SELECT source_id[1]
|
|
FROM public.os_migration_mapping
|
|
WHERE %s = ANY(object_type)
|
|
AND tryton_id = %s
|
|
ORDER BY write_date DESC
|
|
LIMIT 1
|
|
""",
|
|
(object_type, tryton_id)
|
|
)
|
|
|
|
result = cursor.fetchone()
|
|
return result[0] if result else None
|
|
|
|
finally:
|
|
cursor.close()
|
|
|
|
def get_mapping_by_recon_key(self, object_type, recon_key):
|
|
"""
|
|
Retrieve mapping by reconciliation key
|
|
|
|
Args:
|
|
object_type: Type of object
|
|
recon_key: Reconciliation key
|
|
|
|
Returns:
|
|
dict: Mapping record or None if not found
|
|
"""
|
|
cursor = self.connection.cursor()
|
|
|
|
try:
|
|
cursor.execute(
|
|
"""
|
|
SELECT
|
|
object_type[1] as object_type,
|
|
source_id[1] as source_id,
|
|
tryton_model[1] as tryton_model,
|
|
tryton_id,
|
|
recon_key[1] as recon_key,
|
|
write_date
|
|
FROM public.os_migration_mapping
|
|
WHERE %s = ANY(object_type)
|
|
AND %s = ANY(recon_key)
|
|
ORDER BY write_date DESC
|
|
LIMIT 1
|
|
""",
|
|
(object_type, recon_key)
|
|
)
|
|
|
|
result = cursor.fetchone()
|
|
if result:
|
|
return {
|
|
'object_type': result[0],
|
|
'source_id': result[1],
|
|
'tryton_model': result[2],
|
|
'tryton_id': result[3],
|
|
'recon_key': result[4],
|
|
'write_date': result[5]
|
|
}
|
|
return None
|
|
|
|
finally:
|
|
cursor.close()
|
|
|
|
def delete_mappings_by_source(self, object_type, source_ids):
|
|
"""
|
|
Delete mappings by source IDs (useful for re-import)
|
|
|
|
Args:
|
|
object_type: Type of object
|
|
source_ids: List of source IDs to delete
|
|
|
|
Returns:
|
|
int: Number of records deleted
|
|
"""
|
|
if not source_ids:
|
|
return 0
|
|
|
|
cursor = self.connection.cursor()
|
|
|
|
try:
|
|
# Delete records where source_id matches any in the list
|
|
cursor.execute(
|
|
"""
|
|
DELETE FROM public.os_migration_mapping
|
|
WHERE %s = ANY(object_type)
|
|
AND source_id[1] = ANY(%s)
|
|
""",
|
|
(object_type, source_ids)
|
|
)
|
|
|
|
deleted_count = cursor.rowcount
|
|
logger.info(f"Deleted {deleted_count} migration mapping records")
|
|
return deleted_count
|
|
|
|
finally:
|
|
cursor.close()
|
|
|
|
def get_all_mappings(self, object_type=None):
|
|
"""
|
|
Retrieve all mappings, optionally filtered by object type
|
|
|
|
Args:
|
|
object_type: Optional object type filter
|
|
|
|
Returns:
|
|
list: List of mapping dicts
|
|
"""
|
|
cursor = self.connection.cursor()
|
|
|
|
try:
|
|
if object_type:
|
|
cursor.execute(
|
|
"""
|
|
SELECT
|
|
object_type[1] as object_type,
|
|
source_id[1] as source_id,
|
|
tryton_model[1] as tryton_model,
|
|
tryton_id,
|
|
recon_key[1] as recon_key,
|
|
write_date
|
|
FROM public.os_migration_mapping
|
|
WHERE %s = ANY(object_type)
|
|
ORDER BY write_date DESC
|
|
""",
|
|
(object_type,)
|
|
)
|
|
else:
|
|
cursor.execute(
|
|
"""
|
|
SELECT
|
|
object_type[1] as object_type,
|
|
source_id[1] as source_id,
|
|
tryton_model[1] as tryton_model,
|
|
tryton_id,
|
|
recon_key[1] as recon_key,
|
|
write_date
|
|
FROM public.os_migration_mapping
|
|
ORDER BY write_date DESC
|
|
"""
|
|
)
|
|
|
|
results = cursor.fetchall()
|
|
return [
|
|
{
|
|
'object_type': row[0],
|
|
'source_id': row[1],
|
|
'tryton_model': row[2],
|
|
'tryton_id': row[3],
|
|
'recon_key': row[4],
|
|
'write_date': row[5]
|
|
}
|
|
for row in results
|
|
]
|
|
|
|
finally:
|
|
cursor.close()
|
|
|
|
|
|
# Simplified standalone function for quick integration
|
|
def save_migration_mapping(db_config, object_type, source_id, tryton_model,
|
|
tryton_id, recon_key):
|
|
"""
|
|
Standalone function to save a single migration mapping
|
|
|
|
Args:
|
|
db_config: dict with database connection parameters
|
|
object_type: Type of object being migrated
|
|
source_id: ID from source system
|
|
tryton_model: Tryton model name
|
|
tryton_id: Tryton record ID
|
|
recon_key: Reconciliation key for matching
|
|
"""
|
|
with MigrationMapper(db_config) as mapper:
|
|
mapper.save_mapping(object_type, source_id, tryton_model, tryton_id, recon_key)
|