Correcion del CRUD

This commit is contained in:
2026-03-31 10:57:51 -05:00
parent 6dea5b4a23
commit a297592d84
5 changed files with 47 additions and 7 deletions

View File

@@ -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)

View File

@@ -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.

View File

@@ -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):

View File

@@ -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(

View File

@@ -23,7 +23,7 @@
DownloadFTP: 'No',
DescargarFTPoLocal: 'FTP',
MinsDownLFTP: 0,
UsarFechaEmisionFactura: 'false',
UsarFechaEmisionFactura: 0,
Campo18Valsaaim3: 0,
...currentData
});