Correcion del CRUD
This commit is contained in:
@@ -5,8 +5,8 @@ from sqlalchemy.orm import Mapped, mapped_column
|
|||||||
from sqlalchemy.sql import func
|
from sqlalchemy.sql import func
|
||||||
|
|
||||||
|
|
||||||
class TimestampMixin:
|
class BaseTimestampMixin:
|
||||||
"""Mixin for common timestamp fields"""
|
"""Mixin for basic timestamp fields (no soft delete)"""
|
||||||
|
|
||||||
created_at: Mapped[datetime] = mapped_column(
|
created_at: Mapped[datetime] = mapped_column(
|
||||||
DateTime, nullable=False, server_default=func.now()
|
DateTime, nullable=False, server_default=func.now()
|
||||||
@@ -14,6 +14,11 @@ class TimestampMixin:
|
|||||||
updated_at: Mapped[datetime] = mapped_column(
|
updated_at: Mapped[datetime] = mapped_column(
|
||||||
DateTime, nullable=False, server_default=func.now(), onupdate=func.now()
|
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)
|
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.dialects.postgresql import JSONB
|
||||||
from sqlalchemy.orm import Mapped, mapped_column
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
from core.database import Base
|
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.
|
Unified configuration table for Anexo 76.
|
||||||
Replaces 14 legacy tables using a hierarchical JSONB override system.
|
Replaces 14 legacy tables using a hierarchical JSONB override system.
|
||||||
|
|||||||
@@ -117,6 +117,25 @@ class SSisGenSettings(BaseModel):
|
|||||||
mostrarprogramaimmexprosec: Optional[int] = None
|
mostrarprogramaimmexprosec: Optional[int] = None
|
||||||
costounitarioporempaquefac: Optional[int] = None
|
costounitarioporempaquefac: Optional[int] = None
|
||||||
transmitirfacalterna: 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):
|
class SSisGen2Settings(BaseModel):
|
||||||
|
|||||||
@@ -10,7 +10,10 @@ from decimal import Decimal
|
|||||||
def convert_decimals(obj: Any) -> Any:
|
def convert_decimals(obj: Any) -> Any:
|
||||||
"""
|
"""
|
||||||
Recursively converts Decimal objects to floats for JSON serialization.
|
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):
|
if isinstance(obj, list):
|
||||||
return [convert_decimals(i) for i in obj]
|
return [convert_decimals(i) for i in obj]
|
||||||
elif isinstance(obj, dict):
|
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]:
|
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.
|
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 = (
|
stmt = (
|
||||||
select(AppSetting)
|
select(AppSetting)
|
||||||
.where(
|
.where(
|
||||||
or_(
|
or_(
|
||||||
and_(AppSetting.tenant_id.is_(None), AppSetting.company_id.is_(None)),
|
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 == t_id, AppSetting.company_id.is_(None)) if t_id else False,
|
||||||
and_(AppSetting.tenant_id == tenant_id, AppSetting.company_id == company_id),
|
and_(AppSetting.tenant_id == t_id, AppSetting.company_id == c_id) if t_id and c_id else False,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.order_by(
|
.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:
|
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.
|
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})"
|
level_label = f"level(tenant={tenant_id}, company={company_id})"
|
||||||
logger.info(f"UPSERT Settings START: {level_label}, keys_to_update={list(settings.keys())}")
|
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 []
|
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 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)
|
deep_merge(new_settings, settings)
|
||||||
|
|
||||||
# Second pass of conversion (merged results might still have Decimals if original row had them)
|
# Second pass of conversion (merged results might still have Decimals if original row had them)
|
||||||
existing.settings = convert_decimals(new_settings)
|
existing.settings = convert_decimals(new_settings)
|
||||||
|
|
||||||
from sqlalchemy.orm.attributes import flag_modified
|
from sqlalchemy.orm.attributes import flag_modified
|
||||||
flag_modified(existing, "settings")
|
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:
|
else:
|
||||||
logger.info(f"UPSERT: Creating NEW row for {level_label}")
|
logger.info(f"UPSERT: Creating NEW row for {level_label}")
|
||||||
existing = AppSetting(
|
existing = AppSetting(
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
DownloadFTP: 'No',
|
DownloadFTP: 'No',
|
||||||
DescargarFTPoLocal: 'FTP',
|
DescargarFTPoLocal: 'FTP',
|
||||||
MinsDownLFTP: 0,
|
MinsDownLFTP: 0,
|
||||||
UsarFechaEmisionFactura: 'false',
|
UsarFechaEmisionFactura: 0,
|
||||||
Campo18Valsaaim3: 0,
|
Campo18Valsaaim3: 0,
|
||||||
...currentData
|
...currentData
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user