Parametros generales SCAII, con endpint y con la mayoria de pestanias hechas y funcionalaes
This commit is contained in:
137
backend/api/v1/modules/a76/app_settings/service.py
Normal file
137
backend/api/v1/modules/a76/app_settings/service.py
Normal file
@@ -0,0 +1,137 @@
|
||||
from typing import Optional, List, Dict, Any
|
||||
from sqlalchemy import or_, and_, select, case, nulls_first
|
||||
from sqlalchemy.orm import Session
|
||||
from .models import AppSetting
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
from decimal import Decimal
|
||||
def convert_decimals(obj: Any) -> Any:
|
||||
"""
|
||||
Recursively converts Decimal objects to floats for JSON serialization.
|
||||
"""
|
||||
if isinstance(obj, list):
|
||||
return [convert_decimals(i) for i in obj]
|
||||
elif isinstance(obj, dict):
|
||||
return {k: convert_decimals(v) for k, v in obj.items()}
|
||||
elif isinstance(obj, Decimal):
|
||||
return float(obj)
|
||||
return obj
|
||||
|
||||
def deep_merge(dict1: Dict[str, Any], dict2: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
Recursively merges dict2 into dict1.
|
||||
"""
|
||||
for key, value in dict2.items():
|
||||
if key in dict1 and isinstance(dict1[key], dict) and isinstance(value, dict):
|
||||
deep_merge(dict1[key], value)
|
||||
else:
|
||||
dict1[key] = value
|
||||
return dict1
|
||||
|
||||
class AppSettingsService:
|
||||
"""
|
||||
Service to manage hierarchical configuration overrides.
|
||||
Hierarchy: System (Global) -> Tenant -> Company.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def get_resolved_settings(db: Session, tenant_id: int, company_id: int) -> Dict[str, Any]:
|
||||
"""
|
||||
Retrieves settings from all levels (Global -> Tenant -> Company) and merges them.
|
||||
"""
|
||||
stmt = (
|
||||
select(AppSetting)
|
||||
.where(
|
||||
or_(
|
||||
and_(AppSetting.tenant_id.is_(None), AppSetting.company_id.is_(None)),
|
||||
and_(AppSetting.tenant_id == tenant_id, AppSetting.company_id.is_(None)),
|
||||
and_(AppSetting.tenant_id == tenant_id, AppSetting.company_id == company_id),
|
||||
)
|
||||
)
|
||||
.order_by(
|
||||
# Ensure the order is: Global (1) -> Tenant (2) -> Company (3)
|
||||
case(
|
||||
(and_(AppSetting.tenant_id.is_(None), AppSetting.company_id.is_(None)), 1),
|
||||
(and_(AppSetting.tenant_id.is_not(None), AppSetting.company_id.is_(None)), 2),
|
||||
(and_(AppSetting.tenant_id.is_not(None), AppSetting.company_id.is_not(None)), 3),
|
||||
else_=4
|
||||
).asc()
|
||||
)
|
||||
)
|
||||
|
||||
results = db.execute(stmt).scalars().all()
|
||||
logger.info(f"RESOLVE: Found {len(results)} rows for Hierarchy")
|
||||
|
||||
resolved_settings = {}
|
||||
for row in results:
|
||||
level_name = "GLOBAL" if not row.tenant_id else ("TENANT" if not row.company_id else "COMPANY")
|
||||
|
||||
# Use safe data logging to avoid crashes
|
||||
settings_data = row.settings if row.settings else {}
|
||||
keys = list(settings_data.keys()) if isinstance(settings_data, dict) else "not-a-dict"
|
||||
logger.info(f"RESOLVE: Merging {level_name} layer with keys: {keys}")
|
||||
|
||||
if isinstance(settings_data, dict):
|
||||
deep_merge(resolved_settings, settings_data)
|
||||
|
||||
return resolved_settings
|
||||
|
||||
@staticmethod
|
||||
def upsert_settings(db: Session, tenant_id: Optional[int], company_id: Optional[int], settings: Dict[str, Any]) -> AppSetting:
|
||||
"""
|
||||
Inserts or updates settings for a specific level.
|
||||
"""
|
||||
level_label = f"level(tenant={tenant_id}, company={company_id})"
|
||||
logger.info(f"UPSERT Settings START: {level_label}, keys_to_update={list(settings.keys())}")
|
||||
|
||||
# Ensure all Decimals are converted to floats before deep merge and save
|
||||
settings = convert_decimals(settings)
|
||||
|
||||
stmt = select(AppSetting).where(
|
||||
and_(
|
||||
AppSetting.tenant_id == tenant_id if tenant_id is not None else AppSetting.tenant_id.is_(None),
|
||||
AppSetting.company_id == company_id if company_id is not None else AppSetting.company_id.is_(None)
|
||||
)
|
||||
)
|
||||
|
||||
existing = db.execute(stmt).scalar_one_or_none()
|
||||
|
||||
if existing:
|
||||
# Deep merge at the root level (merging categories like ssisgen, ssismex, etc.)
|
||||
logger.info(f"UPSERT: Updating existing row ID={existing.id}")
|
||||
# Create a shallow copy of the top-level dict to ensure SQLAlchemy sees a new reference
|
||||
new_settings = dict(existing.settings) if existing.settings else {}
|
||||
|
||||
# Detailed logging of what's changing
|
||||
for cat, data in settings.items():
|
||||
old_keys = list(new_settings.get(cat, {}).keys())
|
||||
new_keys = list(data.keys()) if isinstance(data, dict) else []
|
||||
logger.info(f"UPSERT: Merging category [{cat}]. Old keys: {old_keys}, New keys to merge/overwrite: {new_keys}")
|
||||
|
||||
deep_merge(new_settings, settings)
|
||||
# Second pass of conversion (merged results might still have Decimals if original row had them)
|
||||
existing.settings = convert_decimals(new_settings)
|
||||
|
||||
from sqlalchemy.orm.attributes import flag_modified
|
||||
flag_modified(existing, "settings")
|
||||
else:
|
||||
logger.info(f"UPSERT: Creating NEW row for {level_label}")
|
||||
existing = AppSetting(
|
||||
tenant_id=tenant_id,
|
||||
company_id=company_id,
|
||||
settings=settings
|
||||
)
|
||||
db.add(existing)
|
||||
|
||||
try:
|
||||
db.commit()
|
||||
db.refresh(existing)
|
||||
logger.info(f"UPSERT SUCCESS: Row ID={existing.id}, Final Settings Hash Keys={list(existing.settings.keys())}")
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
logger.error(f"UPSERT FAILED: {str(e)}")
|
||||
raise e
|
||||
|
||||
return existing
|
||||
Reference in New Issue
Block a user