Correcion del CRUD
This commit is contained in:
@@ -5,8 +5,8 @@ from sqlalchemy.orm import Mapped, mapped_column
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
|
||||
class TimestampMixin:
|
||||
"""Mixin for common timestamp fields"""
|
||||
class BaseTimestampMixin:
|
||||
"""Mixin for basic timestamp fields (no soft delete)"""
|
||||
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, server_default=func.now()
|
||||
@@ -14,6 +14,11 @@ class TimestampMixin:
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, server_default=func.now(), onupdate=func.now()
|
||||
)
|
||||
|
||||
|
||||
class TimestampMixin(BaseTimestampMixin):
|
||||
"""Mixin for common timestamp fields including soft delete"""
|
||||
|
||||
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@ from sqlalchemy import Integer, ForeignKey, UniqueConstraint
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
from core.database import Base
|
||||
from api.v1.common.base_models import TimestampMixin
|
||||
from api.v1.common.base_models import BaseTimestampMixin
|
||||
|
||||
class AppSetting(Base, TimestampMixin):
|
||||
class AppSetting(Base, BaseTimestampMixin):
|
||||
"""
|
||||
Unified configuration table for Anexo 76.
|
||||
Replaces 14 legacy tables using a hierarchical JSONB override system.
|
||||
|
||||
@@ -117,6 +117,25 @@ class SSisGenSettings(BaseModel):
|
||||
mostrarprogramaimmexprosec: Optional[int] = None
|
||||
costounitarioporempaquefac: Optional[int] = None
|
||||
transmitirfacalterna: Optional[int] = None
|
||||
muestra_copias_codbarras: Optional[int] = None
|
||||
activar_revision_fracciones: Optional[int] = None
|
||||
usarcoveenarchsaaim3: Optional[int] = None
|
||||
DownloadFTP: Optional[str] = None
|
||||
MinsDownLFTP: Optional[int] = None
|
||||
DownloadFTPPath: Optional[str] = None
|
||||
DescargarFTPoLocal: Optional[str] = None
|
||||
ServerFTP: Optional[str] = None
|
||||
UserFTP: Optional[str] = None
|
||||
PasswordFTP: Optional[str] = None
|
||||
DirectorioFTP: Optional[str] = None
|
||||
PathLocalParaDescDe: Optional[str] = None
|
||||
AgregarRemplazarAutomatico: Optional[str] = None
|
||||
ActivarProcesoVEquipment: Optional[int] = None
|
||||
ActivarProcesoDesperdicioJDEdwards: Optional[int] = None
|
||||
UsarFechaEmisionFactura: Optional[int] = None
|
||||
Campo18Valsaaim3: Optional[int] = None
|
||||
actvaloragre: Optional[int] = None
|
||||
valoragregadogen: Optional[Decimal] = None
|
||||
|
||||
|
||||
class SSisGen2Settings(BaseModel):
|
||||
|
||||
@@ -10,7 +10,10 @@ from decimal import Decimal
|
||||
def convert_decimals(obj: Any) -> Any:
|
||||
"""
|
||||
Recursively converts Decimal objects to floats for JSON serialization.
|
||||
Handles nested structures and None values.
|
||||
"""
|
||||
if obj is None:
|
||||
return None
|
||||
if isinstance(obj, list):
|
||||
return [convert_decimals(i) for i in obj]
|
||||
elif isinstance(obj, dict):
|
||||
@@ -40,14 +43,19 @@ class AppSettingsService:
|
||||
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.
|
||||
Treats 0 as None for hierarchy resolution.
|
||||
"""
|
||||
# Normalize 0 to None for context-less lookups
|
||||
t_id = tenant_id if tenant_id and tenant_id > 0 else None
|
||||
c_id = company_id if company_id and company_id > 0 else None
|
||||
|
||||
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),
|
||||
and_(AppSetting.tenant_id == t_id, AppSetting.company_id.is_(None)) if t_id else False,
|
||||
and_(AppSetting.tenant_id == t_id, AppSetting.company_id == c_id) if t_id and c_id else False,
|
||||
)
|
||||
)
|
||||
.order_by(
|
||||
@@ -82,7 +90,12 @@ class AppSettingsService:
|
||||
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.
|
||||
Treats 0 as None.
|
||||
"""
|
||||
# Normalize IDs: 0 or None means Global context at that level
|
||||
tenant_id = tenant_id if tenant_id and tenant_id > 0 else None
|
||||
company_id = company_id if company_id and company_id > 0 else None
|
||||
|
||||
level_label = f"level(tenant={tenant_id}, company={company_id})"
|
||||
logger.info(f"UPSERT Settings START: {level_label}, keys_to_update={list(settings.keys())}")
|
||||
|
||||
@@ -110,12 +123,15 @@ class AppSettingsService:
|
||||
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}")
|
||||
|
||||
logger.info(f"UPSERT: Merging {len(settings)} top-level categories into existing row.")
|
||||
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")
|
||||
logger.info(f"UPSERT: Row updated and flagged as modified. Fields in ssisgen root: {list(new_settings.get('ssisgen', {}).keys())[:10]}...")
|
||||
else:
|
||||
logger.info(f"UPSERT: Creating NEW row for {level_label}")
|
||||
existing = AppSetting(
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
DownloadFTP: 'No',
|
||||
DescargarFTPoLocal: 'FTP',
|
||||
MinsDownLFTP: 0,
|
||||
UsarFechaEmisionFactura: 'false',
|
||||
UsarFechaEmisionFactura: 0,
|
||||
Campo18Valsaaim3: 0,
|
||||
...currentData
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user