feat: enhance audit log functionality with tenant_id handling and optional seed data in initialization script
This commit is contained in:
@@ -75,7 +75,9 @@ class FaLineItem(Base, TenantScopedMixin, TimestampMixin):
|
|||||||
omit_annex31: Mapped[Optional[bool]] = mapped_column(Boolean) # OMITITENANEXO31
|
omit_annex31: Mapped[Optional[bool]] = mapped_column(Boolean) # OMITITENANEXO31
|
||||||
|
|
||||||
# --- RELACIÓN ---
|
# --- RELACIÓN ---
|
||||||
master_info: Mapped["LineItem"] = relationship("LineItem", back_populates="fa_data")
|
master_info: Mapped["LineItem"] = relationship(
|
||||||
|
"api.v1.modules.a76.items.models.LineItem", back_populates="fa_data"
|
||||||
|
)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"<FaLineItem(id={self.id}, asset_number='{self.asset_number}')>"
|
return f"<FaLineItem(id={self.id}, asset_number='{self.asset_number}')>"
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
"""
|
"""
|
||||||
Audit Log Events
|
Audit Log Events
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from sqlalchemy import event, inspect
|
from sqlalchemy import event, inspect
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from .services.service import AuditService
|
from .services.service import AuditService
|
||||||
|
from .utils.serialization import serialize_for_json
|
||||||
from core.context import get_user_context
|
from core.context import get_user_context
|
||||||
|
|
||||||
|
|
||||||
def register_audit_listeners(models_to_audit):
|
def register_audit_listeners(models_to_audit):
|
||||||
"""
|
"""
|
||||||
Register SQLAlchemy listeners for given models
|
Register SQLAlchemy listeners for given models
|
||||||
@@ -15,16 +18,23 @@ def register_audit_listeners(models_to_audit):
|
|||||||
event.listen(model, "after_update", after_update_listener)
|
event.listen(model, "after_update", after_update_listener)
|
||||||
event.listen(model, "after_delete", after_delete_listener)
|
event.listen(model, "after_delete", after_delete_listener)
|
||||||
|
|
||||||
|
|
||||||
def _get_current_username():
|
def _get_current_username():
|
||||||
try:
|
try:
|
||||||
context = get_user_context()
|
context = get_user_context()
|
||||||
if context:
|
if context:
|
||||||
# Token usually has 'preferred_username' or 'name' or 'sub'
|
# Token usually has 'preferred_username' or 'name' or 'sub'
|
||||||
return context.get("preferred_username") or context.get("email") or context.get("sub") or "System"
|
return (
|
||||||
|
context.get("preferred_username")
|
||||||
|
or context.get("email")
|
||||||
|
or context.get("sub")
|
||||||
|
or "System"
|
||||||
|
)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
return "System"
|
return "System"
|
||||||
|
|
||||||
|
|
||||||
def after_insert_listener(mapper, connection, target):
|
def after_insert_listener(mapper, connection, target):
|
||||||
"""
|
"""
|
||||||
Listener for INSERT operations
|
Listener for INSERT operations
|
||||||
@@ -33,7 +43,8 @@ def after_insert_listener(mapper, connection, target):
|
|||||||
record_data = {c.name: getattr(target, c.name) for c in mapper.columns}
|
record_data = {c.name: getattr(target, c.name) for c in mapper.columns}
|
||||||
username = _get_current_username()
|
username = _get_current_username()
|
||||||
company_id = getattr(target, "company_id", None)
|
company_id = getattr(target, "company_id", None)
|
||||||
|
tenant_id = getattr(target, "tenant_id", None)
|
||||||
|
|
||||||
# Create a session bound to the connection
|
# Create a session bound to the connection
|
||||||
session = Session(bind=connection)
|
session = Session(bind=connection)
|
||||||
try:
|
try:
|
||||||
@@ -44,24 +55,26 @@ def after_insert_listener(mapper, connection, target):
|
|||||||
record_data=record_data,
|
record_data=record_data,
|
||||||
username=username,
|
username=username,
|
||||||
record_id=str(getattr(target, "id", "")),
|
record_id=str(getattr(target, "id", "")),
|
||||||
company_id=company_id
|
company_id=company_id,
|
||||||
|
tenant_id=tenant_id,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error logging insert: {e}")
|
print(f"Error logging insert: {e}")
|
||||||
finally:
|
finally:
|
||||||
session.close()
|
session.close()
|
||||||
|
|
||||||
|
|
||||||
def after_update_listener(mapper, connection, target):
|
def after_update_listener(mapper, connection, target):
|
||||||
"""
|
"""
|
||||||
Listener for UPDATE operations
|
Listener for UPDATE operations
|
||||||
"""
|
"""
|
||||||
table_name = target.__tablename__
|
table_name = target.__tablename__
|
||||||
|
|
||||||
state = inspect(target)
|
state = inspect(target)
|
||||||
changes = {}
|
changes = {}
|
||||||
old_values = {}
|
old_values = {}
|
||||||
new_values = {}
|
new_values = {}
|
||||||
|
|
||||||
for attr in state.attrs:
|
for attr in state.attrs:
|
||||||
hist = attr.history
|
hist = attr.history
|
||||||
if hist.has_changes():
|
if hist.has_changes():
|
||||||
@@ -74,6 +87,8 @@ def after_update_listener(mapper, connection, target):
|
|||||||
|
|
||||||
record_data = {c.name: getattr(target, c.name) for c in mapper.columns}
|
record_data = {c.name: getattr(target, c.name) for c in mapper.columns}
|
||||||
username = _get_current_username()
|
username = _get_current_username()
|
||||||
|
company_id = getattr(target, "company_id", None)
|
||||||
|
tenant_id = getattr(target, "tenant_id", None)
|
||||||
|
|
||||||
session = Session(bind=connection)
|
session = Session(bind=connection)
|
||||||
try:
|
try:
|
||||||
@@ -84,13 +99,16 @@ def after_update_listener(mapper, connection, target):
|
|||||||
record_data=record_data,
|
record_data=record_data,
|
||||||
username=username,
|
username=username,
|
||||||
record_id=str(getattr(target, "id", "")),
|
record_id=str(getattr(target, "id", "")),
|
||||||
old_values=old_values,
|
old_values=serialize_for_json(old_values),
|
||||||
new_values=new_values
|
new_values=serialize_for_json(new_values),
|
||||||
|
company_id=company_id,
|
||||||
|
tenant_id=tenant_id,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error logging update: {e}")
|
print(f"Error logging update: {e}")
|
||||||
finally:
|
finally:
|
||||||
session.close()
|
session.close()
|
||||||
|
|
||||||
|
|
||||||
def after_delete_listener(mapper, connection, target):
|
def after_delete_listener(mapper, connection, target):
|
||||||
"""
|
"""
|
||||||
@@ -99,7 +117,9 @@ def after_delete_listener(mapper, connection, target):
|
|||||||
table_name = target.__tablename__
|
table_name = target.__tablename__
|
||||||
record_data = {c.name: getattr(target, c.name) for c in mapper.columns}
|
record_data = {c.name: getattr(target, c.name) for c in mapper.columns}
|
||||||
username = _get_current_username()
|
username = _get_current_username()
|
||||||
|
company_id = getattr(target, "company_id", None)
|
||||||
|
tenant_id = getattr(target, "tenant_id", None)
|
||||||
|
|
||||||
session = Session(bind=connection)
|
session = Session(bind=connection)
|
||||||
try:
|
try:
|
||||||
AuditService.log_crud_operation(
|
AuditService.log_crud_operation(
|
||||||
@@ -108,9 +128,11 @@ def after_delete_listener(mapper, connection, target):
|
|||||||
operation_type="DELETE",
|
operation_type="DELETE",
|
||||||
record_data=record_data,
|
record_data=record_data,
|
||||||
username=username,
|
username=username,
|
||||||
record_id=str(getattr(target, "id", ""))
|
record_id=str(getattr(target, "id", "")),
|
||||||
|
company_id=company_id,
|
||||||
|
tenant_id=tenant_id,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error logging delete: {e}")
|
print(f"Error logging delete: {e}")
|
||||||
finally:
|
finally:
|
||||||
session.close()
|
session.close()
|
||||||
|
|||||||
@@ -4,10 +4,18 @@ Audit Log Models
|
|||||||
|
|
||||||
from sqlalchemy import Column, Integer, String, Date, Time, DateTime, Text, Index, func
|
from sqlalchemy import Column, Integer, String, Date, Time, DateTime, Text, Index, func
|
||||||
from sqlalchemy.dialects.postgresql import JSONB, ARRAY
|
from sqlalchemy.dialects.postgresql import JSONB, ARRAY
|
||||||
|
from api.v1.common.base_models import TenantScopedMixin, TimestampMixin
|
||||||
from core.database import Base
|
from core.database import Base
|
||||||
|
|
||||||
class AuditLog(Base):
|
class AuditLog(Base, TenantScopedMixin, TimestampMixin):
|
||||||
__tablename__ = "audit_logs"
|
__tablename__ = "audit_logs"
|
||||||
|
__table_args__ = (
|
||||||
|
Index('idx_audit_username_date', 'username', 'date'),
|
||||||
|
Index('idx_audit_procedure_date', 'procedure', 'date'),
|
||||||
|
Index('idx_audit_system_timestamp', 'system', 'timestamp'),
|
||||||
|
Index('idx_audit_table_record', 'table_name', 'record_id'),
|
||||||
|
{"schema": "a76"} # Use the a76 schema for audit logs
|
||||||
|
)
|
||||||
|
|
||||||
# Primary Key
|
# Primary Key
|
||||||
spec_id = Column(Integer, primary_key=True, autoincrement=True)
|
spec_id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
@@ -23,8 +31,6 @@ class AuditLog(Base):
|
|||||||
# Technical Columns
|
# Technical Columns
|
||||||
timestamp = Column(DateTime(timezone=True), nullable=False, index=True) # Combined for queries
|
timestamp = Column(DateTime(timezone=True), nullable=False, index=True) # Combined for queries
|
||||||
system = Column(String(20), nullable=False, index=True, default="SCAF")
|
system = Column(String(20), nullable=False, index=True, default="SCAF")
|
||||||
company_id = Column(Integer, nullable=True, index=True)
|
|
||||||
tenant_id = Column(Integer, nullable=True, index=True)
|
|
||||||
|
|
||||||
# Traceability
|
# Traceability
|
||||||
table_name = Column(String(100), nullable=True, index=True)
|
table_name = Column(String(100), nullable=True, index=True)
|
||||||
@@ -42,15 +48,4 @@ class AuditLog(Base):
|
|||||||
endpoint = Column(String(500), nullable=True)
|
endpoint = Column(String(500), nullable=True)
|
||||||
request_method = Column(String(10), nullable=True)
|
request_method = Column(String(10), nullable=True)
|
||||||
session_id = Column(String(50), nullable=True, index=True)
|
session_id = Column(String(50), nullable=True, index=True)
|
||||||
execution_time_ms = Column(Integer, nullable=True)
|
execution_time_ms = Column(Integer, nullable=True)
|
||||||
|
|
||||||
# Metadata
|
|
||||||
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
|
|
||||||
|
|
||||||
# Composite Indexes for common filters
|
|
||||||
__table_args__ = (
|
|
||||||
Index('idx_audit_username_date', 'username', 'date'),
|
|
||||||
Index('idx_audit_procedure_date', 'procedure', 'date'),
|
|
||||||
Index('idx_audit_system_timestamp', 'system', 'timestamp'),
|
|
||||||
Index('idx_audit_table_record', 'table_name', 'record_id'),
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -86,7 +86,8 @@ class AuditService:
|
|||||||
# Context
|
# Context
|
||||||
ip_address: Optional[str] = None,
|
ip_address: Optional[str] = None,
|
||||||
user_agent: Optional[str] = None,
|
user_agent: Optional[str] = None,
|
||||||
company_id: Optional[int] = None
|
company_id: Optional[int] = None,
|
||||||
|
tenant_id: Optional[int] = None
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
High-level wrapper to log CRUD operations automatically mapping to Legacy format
|
High-level wrapper to log CRUD operations automatically mapping to Legacy format
|
||||||
@@ -149,7 +150,8 @@ class AuditService:
|
|||||||
changed_fields=changed_fields,
|
changed_fields=changed_fields,
|
||||||
ip_address=ip_address,
|
ip_address=ip_address,
|
||||||
user_agent=user_agent,
|
user_agent=user_agent,
|
||||||
company_id=company_id
|
company_id=company_id,
|
||||||
|
tenant_id=tenant_id
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
7
backend/api/v1/modules/a76/audit_log/utils/__init__.py
Normal file
7
backend/api/v1/modules/a76/audit_log/utils/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
"""
|
||||||
|
Audit Log Utilities
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .serialization import serialize_for_json
|
||||||
|
|
||||||
|
__all__ = ["serialize_for_json"]
|
||||||
44
backend/api/v1/modules/a76/audit_log/utils/serialization.py
Normal file
44
backend/api/v1/modules/a76/audit_log/utils/serialization.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
"""
|
||||||
|
Serialization utilities for audit logs
|
||||||
|
"""
|
||||||
|
|
||||||
|
from datetime import date, datetime, time
|
||||||
|
from decimal import Decimal
|
||||||
|
from uuid import UUID
|
||||||
|
from typing import Any, Dict
|
||||||
|
|
||||||
|
|
||||||
|
def serialize_value(value: Any) -> Any:
|
||||||
|
"""
|
||||||
|
Convert a Python value to a JSON-serializable type
|
||||||
|
"""
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
elif isinstance(value, (date, datetime)):
|
||||||
|
return value.isoformat()
|
||||||
|
elif isinstance(value, time):
|
||||||
|
return value.isoformat()
|
||||||
|
elif isinstance(value, Decimal):
|
||||||
|
return float(value)
|
||||||
|
elif isinstance(value, UUID):
|
||||||
|
return str(value)
|
||||||
|
elif isinstance(value, bytes):
|
||||||
|
return value.decode("utf-8", errors="replace")
|
||||||
|
elif isinstance(value, (list, tuple)):
|
||||||
|
return [serialize_value(item) for item in value]
|
||||||
|
elif isinstance(value, dict):
|
||||||
|
return {key: serialize_value(val) for key, val in value.items()}
|
||||||
|
else:
|
||||||
|
# For any other type, try to return as-is (str, int, float, bool, None)
|
||||||
|
# If it fails JSON serialization later, at least we tried
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def serialize_for_json(data: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Recursively serialize a dictionary for JSON storage
|
||||||
|
"""
|
||||||
|
if not data:
|
||||||
|
return data
|
||||||
|
|
||||||
|
return {key: serialize_value(value) for key, value in data.items()}
|
||||||
@@ -6,14 +6,7 @@ SQLAlchemy v2 - Annex 24 Compliance
|
|||||||
from typing import Optional, TYPE_CHECKING
|
from typing import Optional, TYPE_CHECKING
|
||||||
from core.database import Base
|
from core.database import Base
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from sqlalchemy import (
|
from sqlalchemy import Boolean, String, Integer, Numeric, SmallInteger, ForeignKey
|
||||||
Boolean,
|
|
||||||
String,
|
|
||||||
Integer,
|
|
||||||
Numeric,
|
|
||||||
SmallInteger,
|
|
||||||
ForeignKey
|
|
||||||
)
|
|
||||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||||
from api.v1.common.base_models import TenantScopedMixin, TimestampMixin
|
from api.v1.common.base_models import TenantScopedMixin, TimestampMixin
|
||||||
from core.database import Base
|
from core.database import Base
|
||||||
@@ -21,7 +14,7 @@ from core.database import Base
|
|||||||
from api.v1.modules.a76.classes.models import Class
|
from api.v1.modules.a76.classes.models import Class
|
||||||
from api.v1.modules.a76.general_catalogs.units_of_measure.models import UnitOfMeasure
|
from api.v1.modules.a76.general_catalogs.units_of_measure.models import UnitOfMeasure
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .line_financials.models import LineFinancial
|
from .line_financials.models import LineFinancial
|
||||||
from .line_quantities.models import LineQuantity
|
from .line_quantities.models import LineQuantity
|
||||||
from .line_customs.models import LineCustom
|
from .line_customs.models import LineCustom
|
||||||
@@ -41,17 +34,20 @@ class LineItem(Base, TenantScopedMixin, TimestampMixin):
|
|||||||
Unified item header table for all import/export operations
|
Unified item header table for all import/export operations
|
||||||
Consolidates headers from both SCAF and SCAII systems
|
Consolidates headers from both SCAF and SCAII systems
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__tablename__ = "item_lines"
|
__tablename__ = "item_lines"
|
||||||
__table_args__ = {
|
__table_args__ = {
|
||||||
"schema": "a76",
|
"schema": "a76",
|
||||||
}
|
}
|
||||||
|
|
||||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||||
invoice_id: Mapped[int] = mapped_column(ForeignKey("a76.invoice_header.id")) # CONSECUTIVO
|
invoice_id: Mapped[int] = mapped_column(
|
||||||
|
ForeignKey("a76.invoice_header.id")
|
||||||
|
) # CONSECUTIVO
|
||||||
|
|
||||||
line_number: Mapped[int] = mapped_column(Integer) # LINEAIMPO/LINEAEXPO/LINEA
|
line_number: Mapped[int] = mapped_column(Integer) # LINEAIMPO/LINEAEXPO/LINEA
|
||||||
|
|
||||||
# Part identification
|
# Part identification
|
||||||
part_number_id: Mapped[Optional[int]] = mapped_column(
|
part_number_id: Mapped[Optional[int]] = mapped_column(
|
||||||
Integer, ForeignKey("a76.parts.id")
|
Integer, ForeignKey("a76.parts.id")
|
||||||
) # NUMPARTE
|
) # NUMPARTE
|
||||||
@@ -170,30 +166,28 @@ class LineItem(Base, TenantScopedMixin, TimestampMixin):
|
|||||||
pallet2: Mapped[Optional[int]] = mapped_column(SmallInteger) # PALLET2
|
pallet2: Mapped[Optional[int]] = mapped_column(SmallInteger) # PALLET2
|
||||||
|
|
||||||
# Wildcard field
|
# Wildcard field
|
||||||
wildcard_field: Mapped[Optional[str]] = mapped_column(String(100)) # CAMPOCOMODIN
|
wildcard_field: Mapped[Optional[str]] = mapped_column(String(100)) # CAMPOCOMODIN
|
||||||
|
|
||||||
# Item references
|
# Item references
|
||||||
reference_number: Mapped[Optional[str]] = mapped_column(
|
reference_number: Mapped[Optional[str]] = mapped_column(String(20)) # NUMREFERENCIA
|
||||||
String(20)) # NUMREFERENCIA
|
order: Mapped[Optional[str]] = mapped_column(String(50)) # ORDENCOMPRA / ORDENVENTA
|
||||||
order: Mapped[Optional[str]] = mapped_column(
|
|
||||||
String(50)) # ORDENCOMPRA / ORDENVENTA
|
|
||||||
guide_number: Mapped[Optional[str]] = mapped_column(
|
guide_number: Mapped[Optional[str]] = mapped_column(
|
||||||
String(50)) # NUMEROGUIA/NUMERODEGUIA
|
String(50)
|
||||||
|
) # NUMEROGUIA/NUMERODEGUIA
|
||||||
|
|
||||||
# Dates
|
# Dates
|
||||||
depreciation_date: Mapped[Optional[int]] = mapped_column(
|
depreciation_date: Mapped[Optional[int]] = mapped_column(
|
||||||
Integer) # FECHADEPRECIACION
|
Integer
|
||||||
|
) # FECHADEPRECIACION
|
||||||
|
|
||||||
# Administrative fields
|
# Administrative fields
|
||||||
rectification: Mapped[Optional[bool]] = mapped_column(
|
rectification: Mapped[Optional[bool]] = mapped_column(Boolean) # RECTIFICACION
|
||||||
Boolean) # RECTIFICACION
|
|
||||||
warehouse: Mapped[Optional[str]] = mapped_column(String(30)) # BODEGA
|
warehouse: Mapped[Optional[str]] = mapped_column(String(30)) # BODEGA
|
||||||
location: Mapped[Optional[str]] = mapped_column(
|
location: Mapped[Optional[str]] = mapped_column(String(200)) # LOCALIZACION
|
||||||
String(200)) # LOCALIZACION
|
|
||||||
|
|
||||||
invoice: Mapped["InvoiceHeader"] = relationship("InvoiceHeader")
|
invoice: Mapped["InvoiceHeader"] = relationship("InvoiceHeader")
|
||||||
|
|
||||||
# Relationships
|
# Relationships
|
||||||
financial: Mapped[Optional["LineFinancial"]] = relationship(
|
financial: Mapped[Optional["LineFinancial"]] = relationship(
|
||||||
back_populates="line", cascade="all, delete-orphan", uselist=False
|
back_populates="line", cascade="all, delete-orphan", uselist=False
|
||||||
)
|
)
|
||||||
@@ -220,7 +214,7 @@ class LineItem(Base, TenantScopedMixin, TimestampMixin):
|
|||||||
viewonly=True,
|
viewonly=True,
|
||||||
)
|
)
|
||||||
fa_data: Mapped[Optional["FaLineItem"]] = relationship(
|
fa_data: Mapped[Optional["FaLineItem"]] = relationship(
|
||||||
"FaLineItem",
|
"api.v1.modules.a24.fa.fa_item_lines.models.FaLineItem",
|
||||||
back_populates="master_info",
|
back_populates="master_info",
|
||||||
cascade="all, delete-orphan",
|
cascade="all, delete-orphan",
|
||||||
uselist=False,
|
uselist=False,
|
||||||
@@ -231,6 +225,7 @@ class LineItem(Base, TenantScopedMixin, TimestampMixin):
|
|||||||
viewonly=True,
|
viewonly=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# SUPPORTING TABLES
|
# SUPPORTING TABLES
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
@@ -241,6 +236,7 @@ class PackingList(Base, TenantScopedMixin, TimestampMixin):
|
|||||||
Packing list items
|
Packing list items
|
||||||
From: SPartidasPackingList
|
From: SPartidasPackingList
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__tablename__ = "packing_lists"
|
__tablename__ = "packing_lists"
|
||||||
__table_args__ = {
|
__table_args__ = {
|
||||||
"schema": "a76",
|
"schema": "a76",
|
||||||
@@ -249,7 +245,8 @@ class PackingList(Base, TenantScopedMixin, TimestampMixin):
|
|||||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||||
item_line_id: Mapped[int] = mapped_column(Integer) # LINEA
|
item_line_id: Mapped[int] = mapped_column(Integer) # LINEA
|
||||||
packing_list_number: Mapped[Optional[str]] = mapped_column(
|
packing_list_number: Mapped[Optional[str]] = mapped_column(
|
||||||
String(100)) # NUMPACKINGLIST
|
String(100)
|
||||||
|
) # NUMPACKINGLIST
|
||||||
|
|
||||||
|
|
||||||
class CTMReceipt(Base, TenantScopedMixin, TimestampMixin):
|
class CTMReceipt(Base, TenantScopedMixin, TimestampMixin):
|
||||||
@@ -257,6 +254,7 @@ class CTMReceipt(Base, TenantScopedMixin, TimestampMixin):
|
|||||||
CTM Receipt lines (temporary manufacturing)
|
CTM Receipt lines (temporary manufacturing)
|
||||||
From: SPartidasReciboCTM
|
From: SPartidasReciboCTM
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__tablename__ = "ctm_receipts"
|
__tablename__ = "ctm_receipts"
|
||||||
__table_args__ = {
|
__table_args__ = {
|
||||||
"schema": "a76",
|
"schema": "a76",
|
||||||
@@ -264,11 +262,11 @@ class CTMReceipt(Base, TenantScopedMixin, TimestampMixin):
|
|||||||
|
|
||||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||||
receipt_line: Mapped[int] = mapped_column(
|
receipt_line: Mapped[int] = mapped_column(
|
||||||
ForeignKey("a76.item_lines.id")) # LINEARECIBO
|
ForeignKey("a76.item_lines.id")
|
||||||
|
) # LINEARECIBO
|
||||||
|
|
||||||
option: Mapped[Optional[str]] = mapped_column(String(3)) # OPCION
|
option: Mapped[Optional[str]] = mapped_column(String(3)) # OPCION
|
||||||
exit_invoice: Mapped[Optional[str]] = mapped_column(
|
exit_invoice: Mapped[Optional[str]] = mapped_column(String(19)) # FACTURASALIDA
|
||||||
String(19)) # FACTURASALIDA
|
|
||||||
|
|
||||||
|
|
||||||
class SubassemblyEntry(Base, TenantScopedMixin, TimestampMixin):
|
class SubassemblyEntry(Base, TenantScopedMixin, TimestampMixin):
|
||||||
@@ -276,6 +274,7 @@ class SubassemblyEntry(Base, TenantScopedMixin, TimestampMixin):
|
|||||||
Subassembly/Submanufacturing Entry lines
|
Subassembly/Submanufacturing Entry lines
|
||||||
From: SPartidasEntradaSM
|
From: SPartidasEntradaSM
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__tablename__ = "subassembly_entries"
|
__tablename__ = "subassembly_entries"
|
||||||
__table_args__ = {
|
__table_args__ = {
|
||||||
"schema": "a76",
|
"schema": "a76",
|
||||||
@@ -283,10 +282,10 @@ class SubassemblyEntry(Base, TenantScopedMixin, TimestampMixin):
|
|||||||
|
|
||||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||||
remission_line: Mapped[int] = mapped_column(Integer) # LINEAREMISION
|
remission_line: Mapped[int] = mapped_column(Integer) # LINEAREMISION
|
||||||
exit_invoice: Mapped[Optional[str]] = mapped_column(
|
exit_invoice: Mapped[Optional[str]] = mapped_column(String(15)) # FACTURASALIDA
|
||||||
String(15)) # FACTURASALIDA
|
|
||||||
exit_line: Mapped[Optional[int]] = mapped_column(Integer) # LINEASALIDA
|
exit_line: Mapped[Optional[int]] = mapped_column(Integer) # LINEASALIDA
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# INDEXES AND CONSTRAINTS
|
# INDEXES AND CONSTRAINTS
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|||||||
@@ -12,6 +12,11 @@
|
|||||||
# 6. Relación usuario-tenant en tabla user_tenants
|
# 6. Relación usuario-tenant en tabla user_tenants
|
||||||
# 7. Actualización del tenant_id del usuario con el valor real
|
# 7. Actualización del tenant_id del usuario con el valor real
|
||||||
# 8. Licencia Enterprise para el tenant (ilimitada, 1 año de vigencia)
|
# 8. Licencia Enterprise para el tenant (ilimitada, 1 año de vigencia)
|
||||||
|
# 9. [OPCIONAL] Datos iniciales de ejemplo si se pasa --seed-data
|
||||||
|
#
|
||||||
|
# Uso:
|
||||||
|
# ./init_first_time.sh # Solo configuración básica
|
||||||
|
# ./init_first_time.sh --seed-data # Configuración + datos de ejemplo
|
||||||
#
|
#
|
||||||
# Requisitos:
|
# Requisitos:
|
||||||
# - Keycloak corriendo en http://localhost:8080
|
# - Keycloak corriendo en http://localhost:8080
|
||||||
@@ -28,6 +33,22 @@ set -euo pipefail # Modo strict: exit on error, undefined vars, pipe failures
|
|||||||
# Trap para cleanup en caso de error
|
# Trap para cleanup en caso de error
|
||||||
trap 'echo -e "\n${RED}✗ Error en línea $LINENO. Script abortado.${NC}" >&2' ERR
|
trap 'echo -e "\n${RED}✗ Error en línea $LINENO. Script abortado.${NC}" >&2' ERR
|
||||||
|
|
||||||
|
# Parsear argumentos
|
||||||
|
SEED_DATA=false
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
--seed-data)
|
||||||
|
SEED_DATA=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Uso: $0 [--seed-data]"
|
||||||
|
echo " --seed-data: Carga datos de ejemplo en las tablas"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
# Colores para output
|
# Colores para output
|
||||||
RED='\033[0;31m'
|
RED='\033[0;31m'
|
||||||
GREEN='\033[0;32m'
|
GREEN='\033[0;32m'
|
||||||
@@ -112,6 +133,171 @@ create_tenant_mapper() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Funciones de seed data
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
# Insertar datos de ejemplo para customs_brokers
|
||||||
|
seed_customs_brokers() {
|
||||||
|
echo " → Insertando customs brokers..."
|
||||||
|
exec_pg_sql "
|
||||||
|
INSERT INTO a76.customs_brokers (tenant_id, company_id, type, broker_key, name, address, postal_code, city, state, phone, email, country, tax_id, license, company, contact, created_at, updated_at)
|
||||||
|
VALUES
|
||||||
|
(${TENANT_ID}, ${COMPANY_ID}, 'persona', 'CB001', 'Agente Aduanal García', 'Av. Reforma 123', '01000', 'Ciudad de México', 'CDMX', '5555555555', 'garcia@aduanas.com', 'MEX', 'GAAR800101ABC', '1234', 'García y Asociados', 'Juan García', now(), now()),
|
||||||
|
(${TENANT_ID}, ${COMPANY_ID}, 'persona', 'CB002', 'Agente Aduanal López', 'Blvd. Díaz Ordaz 456', '22000', 'Tijuana', 'BC', '6641234567', 'lopez@customs.com', 'MEX', 'LOPL750505XYZ', '2345', 'López Customs', 'María López', now(), now()),
|
||||||
|
(${TENANT_ID}, ${COMPANY_ID}, 'persona', 'CB003', 'Agente Aduanal Martínez', 'Calle Industria 789', '45000', 'Guadalajara', 'JAL', '3339876543', 'martinez@broker.com', 'MEX', 'MARM850315DEF', '3456', 'Martínez Brokerage', 'Pedro Martínez', now(), now())
|
||||||
|
ON CONFLICT (broker_key, tenant_id, company_id) DO NOTHING;
|
||||||
|
" >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Insertar datos de ejemplo para clients_and_providers
|
||||||
|
seed_clients_and_providers() {
|
||||||
|
echo " → Insertando clientes y proveedores..."
|
||||||
|
exec_pg_sql "
|
||||||
|
INSERT INTO a76.clients_and_providers (tenant_id, company_id, type_nat_foreign, name, short_name, rfc, client_or_provider, web_key, is_active, created_at, updated_at)
|
||||||
|
VALUES
|
||||||
|
(${TENANT_ID}, ${COMPANY_ID}, 'N', 'Proveedor Tecnológico SA de CV', 'PROVTECH', 'PTE901201ABC', 'BOTH', 'PROV001', true, now(), now()),
|
||||||
|
(${TENANT_ID}, ${COMPANY_ID}, 'N', 'Cliente Industrial del Norte SA', 'CINORTE', 'CIN850615XYZ', 'BOTH', 'CLI001', true, now(), now()),
|
||||||
|
(${TENANT_ID}, ${COMPANY_ID}, 'E', 'Global Supplies Inc', 'GLOBSUP', 'GSI123456789', 'BOTH', 'BOTH001', true, now(), now()),
|
||||||
|
(${TENANT_ID}, ${COMPANY_ID}, 'N', 'Manufacturas del Bajío SA', 'MANBAJIO', 'MDB920310DEF', 'BOTH', 'CLI002', true, now(), now())
|
||||||
|
RETURNING id;
|
||||||
|
" >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Insertar datos de ejemplo para packages
|
||||||
|
seed_packages() {
|
||||||
|
echo " → Insertando tipos de paquete..."
|
||||||
|
exec_pg_sql "
|
||||||
|
INSERT INTO a76.packages (tenant_id, company_id, key, description_es, description_en, weight_unit, plurals, plural_in, code_ace, code_aamex, created_at, updated_at)
|
||||||
|
VALUES
|
||||||
|
(${TENANT_ID}, ${COMPANY_ID}, 'PK01', 'Caja de Cartón', 'Cardboard Box', 0.5, 'CAJS', 'BOXS', 'CB01', 'CAJA001', now(), now()),
|
||||||
|
(${TENANT_ID}, ${COMPANY_ID}, 'PK02', 'Pallet de Madera', 'Wooden Pallet', 15.0, 'PLTS', 'PLTS', 'WP01', 'PALL001', now(), now()),
|
||||||
|
(${TENANT_ID}, ${COMPANY_ID}, 'PK03', 'Tambor Metálico', 'Metal Drum', 10.0, 'TMBS', 'DRMS', 'MD01', 'TAMB001', now(), now()),
|
||||||
|
(${TENANT_ID}, ${COMPANY_ID}, 'PK04', 'Contenedor', 'Container', 2000.0, 'CONT', 'CONT', 'CT01', 'CONT001', now(), now())
|
||||||
|
ON CONFLICT (tenant_id, company_id, key) DO NOTHING;
|
||||||
|
" >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Insertar datos de ejemplo para classes
|
||||||
|
seed_classes() {
|
||||||
|
echo " → Insertando clases..."
|
||||||
|
exec_pg_sql "INSERT INTO a76.classes (tenant_id, company_id, class_code, description_es, description_en, material_key, unit_of_measure, fraction, us_fraction, created_at, updated_at) VALUES (${TENANT_ID}, ${COMPANY_ID}, 'CLS001', 'Componentes Electrónicos', 'Electronic Components', 'MP', 'PZA', '8542.31.01', '8542.31.0000', now(), now()), (${TENANT_ID}, ${COMPANY_ID}, 'CLS002', 'Partes Automotrices', 'Automotive Parts', 'MP', 'KGS', '8708.29.99', '8708.29.9900', now(), now()), (${TENANT_ID}, ${COMPANY_ID}, 'CLS003', 'Textiles y Telas', 'Textiles and Fabrics', 'MP', 'MT', '5407.20.01', '5407.20.0100', now(), now()), (${TENANT_ID}, ${COMPANY_ID}, 'CLS004', 'Equipo de Computación', 'Computer Equipment', 'MP', 'PZA', '8471.30.01', '8471.30.0100', now(), now()) ON CONFLICT (tenant_id, company_id, class_code) DO NOTHING;" >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Insertar datos de ejemplo para parts
|
||||||
|
seed_parts() {
|
||||||
|
echo " → Insertando partes/componentes..."
|
||||||
|
|
||||||
|
# Obtener un client_id para asociar las partes
|
||||||
|
local client_id
|
||||||
|
client_id=$(exec_pg_sql "SELECT id FROM a76.clients_and_providers WHERE tenant_id = ${TENANT_ID} AND company_id = ${COMPANY_ID} LIMIT 1;" | xargs)
|
||||||
|
|
||||||
|
if [ -n "$client_id" ]; then
|
||||||
|
exec_pg_sql "INSERT INTO a76.parts (tenant_id, company_id, client_id, part_number, description_spanish, description_english, part_class, currency_key, unit_of_measure, unit_cost, fraction, us_fraction, created_at, updated_at) VALUES (${TENANT_ID}, ${COMPANY_ID}, ${client_id}, 'PART-001', 'Microcontrolador ARM Cortex-M4', 'ARM Cortex-M4 Microcontroller', 'CLS001', 'USD', 'PZA', 15.50, '8542.31.01', '8542.31.0000', now(), now()), (${TENANT_ID}, ${COMPANY_ID}, ${client_id}, 'PART-002', 'Filtro de Aceite Automotriz', 'Automotive Oil Filter', 'CLS002', 'USD', 'PZA', 8.75, '8421.23.01', '8421.23.0100', now(), now()), (${TENANT_ID}, ${COMPANY_ID}, ${client_id}, 'PART-003', 'Tela de Algodón para Tapicería', 'Cotton Upholstery Fabric', 'CLS003', 'USD', 'MT', 12.00, '5208.31.01', '5208.31.0100', now(), now()), (${TENANT_ID}, ${COMPANY_ID}, ${client_id}, 'PART-004', 'Disco Duro SSD 500GB', '500GB SSD Hard Drive', 'CLS004', 'USD', 'PZA', 65.00, '8471.70.01', '8471.70.0100', now(), now()), (${TENANT_ID}, ${COMPANY_ID}, ${client_id}, 'PART-005', 'Sensor de Temperatura Digital', 'Digital Temperature Sensor', 'CLS001', 'USD', 'PZA', 5.25, '9025.19.01', '9025.19.0100', now(), now()) ON CONFLICT (tenant_id, company_id, part_number) DO NOTHING;" >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Insertar datos de ejemplo para pedimentos
|
||||||
|
seed_pedimentos() {
|
||||||
|
echo " → Insertando pedimentos..."
|
||||||
|
|
||||||
|
# Primero obtener IDs de clientes
|
||||||
|
local client_ids
|
||||||
|
client_ids=$(exec_pg_sql "SELECT id FROM a76.clients_and_providers WHERE tenant_id = ${TENANT_ID} AND company_id = ${COMPANY_ID} AND client_or_provider IN ('CLIENT', 'BOTH') LIMIT 2;")
|
||||||
|
local client_id_1=$(echo "$client_ids" | sed -n '1p' | xargs)
|
||||||
|
local client_id_2=$(echo "$client_ids" | sed -n '2p' | xargs)
|
||||||
|
|
||||||
|
if [ -n "$client_id_1" ]; then
|
||||||
|
exec_pg_sql "INSERT INTO a76.pedimentos (tenant_id, company_id, year, customs_office, license, pedimento_number, client_id, operation_type, pedimento_type, pedimento_code, regime, status, created_at, updated_at) VALUES (${TENANT_ID}, ${COMPANY_ID}, '24', '47', '3807', '8001234', ${client_id_1}, 'imp', 'normal', 'V1', 'ITE', 'draft', now(), now()), (${TENANT_ID}, ${COMPANY_ID}, '24', '47', '3807', '8001235', ${client_id_1}, 'exp', 'normal', 'V1', 'ETE', 'draft', now(), now()) ON CONFLICT (tenant_id, company_id, year, customs_office, license, pedimento_number) DO NOTHING;" >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$client_id_2" ]; then
|
||||||
|
exec_pg_sql "INSERT INTO a76.pedimentos (tenant_id, company_id, year, customs_office, license, pedimento_number, client_id, operation_type, pedimento_type, pedimento_code, regime, status, created_at, updated_at) VALUES (${TENANT_ID}, ${COMPANY_ID}, '24', '47', '3807', '8001236', ${client_id_2}, 'imp', 'consolidated', 'V1', 'ITE', 'draft', now(), now()) ON CONFLICT (tenant_id, company_id, year, customs_office, license, pedimento_number) DO NOTHING;" >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Insertar datos de ejemplo para invoices
|
||||||
|
seed_invoices() {
|
||||||
|
echo " → Insertando facturas..."
|
||||||
|
|
||||||
|
# Obtener IDs de clientes/proveedores
|
||||||
|
local provider_ids
|
||||||
|
provider_ids=$(exec_pg_sql "SELECT id FROM a76.clients_and_providers WHERE tenant_id = ${TENANT_ID} AND company_id = ${COMPANY_ID} AND client_or_provider IN ('PROVIDER', 'BOTH') LIMIT 2;")
|
||||||
|
local provider_id_1=$(echo "$provider_ids" | sed -n '1p' | xargs)
|
||||||
|
local provider_id_2=$(echo "$provider_ids" | sed -n '2p' | xargs)
|
||||||
|
|
||||||
|
# Insertar facturas en invoice_header (mínimo requerido)
|
||||||
|
exec_pg_sql "INSERT INTO a76.invoice_header (tenant_id, company_id, system, operation_type, invoice_type, invoice_number, invoice_date, is_updated, created_at, updated_at) VALUES (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'imp', 'DEF', 'INV-2024-001', '2024-01-10', false, now(), now()), (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'exp', 'EXDEF', 'INV-2024-002', '2024-02-15', false, now(), now()), (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'imp', 'TEM', 'INV-2024-003', '2024-03-05', false, now(), now()) ON CONFLICT DO NOTHING;" >/dev/null 2>&1
|
||||||
|
|
||||||
|
# Obtener IDs de las facturas recién creadas
|
||||||
|
local invoice_ids
|
||||||
|
invoice_ids=$(exec_pg_sql "SELECT id FROM a76.invoice_header WHERE tenant_id = ${TENANT_ID} AND company_id = ${COMPANY_ID} AND invoice_number IN ('INV-2024-001', 'INV-2024-002', 'INV-2024-003') ORDER BY invoice_number;")
|
||||||
|
local invoice_id_1=$(echo "$invoice_ids" | sed -n '1p' | xargs)
|
||||||
|
local invoice_id_2=$(echo "$invoice_ids" | sed -n '2p' | xargs)
|
||||||
|
local invoice_id_3=$(echo "$invoice_ids" | sed -n '3p' | xargs)
|
||||||
|
|
||||||
|
# Insertar datos financieros para las facturas
|
||||||
|
if [ -n "$invoice_id_1" ]; then
|
||||||
|
exec_pg_sql "INSERT INTO a76.invoice_financials (tenant_id, company_id, invoice_id, currency, exchange_rate, created_at, updated_at) VALUES (${TENANT_ID}, ${COMPANY_ID}, ${invoice_id_1}, 'foreign', 17.50, now(), now()) ON CONFLICT DO NOTHING;" >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$invoice_id_2" ]; then
|
||||||
|
exec_pg_sql "INSERT INTO a76.invoice_financials (tenant_id, company_id, invoice_id, currency, exchange_rate, created_at, updated_at) VALUES (${TENANT_ID}, ${COMPANY_ID}, ${invoice_id_2}, 'foreign', 17.45, now(), now()) ON CONFLICT DO NOTHING;" >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$invoice_id_3" ]; then
|
||||||
|
exec_pg_sql "INSERT INTO a76.invoice_financials (tenant_id, company_id, invoice_id, currency, exchange_rate, created_at, updated_at) VALUES (${TENANT_ID}, ${COMPANY_ID}, ${invoice_id_3}, 'local', 1.00, now(), now()) ON CONFLICT DO NOTHING;" >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Insertar datos de compliance mexicano con proveedores
|
||||||
|
if [ -n "$invoice_id_1" ] && [ -n "$provider_id_1" ]; then
|
||||||
|
exec_pg_sql "INSERT INTO a76.invoice_compliance_mx (tenant_id, company_id, invoice_id, provider_id, created_at, updated_at) VALUES (${TENANT_ID}, ${COMPANY_ID}, ${invoice_id_1}, ${provider_id_1}, now(), now()) ON CONFLICT DO NOTHING;" >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$invoice_id_2" ] && [ -n "$provider_id_1" ]; then
|
||||||
|
exec_pg_sql "INSERT INTO a76.invoice_compliance_mx (tenant_id, company_id, invoice_id, provider_id, created_at, updated_at) VALUES (${TENANT_ID}, ${COMPANY_ID}, ${invoice_id_2}, ${provider_id_1}, now(), now()) ON CONFLICT DO NOTHING;" >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$invoice_id_3" ] && [ -n "$provider_id_2" ]; then
|
||||||
|
exec_pg_sql "INSERT INTO a76.invoice_compliance_mx (tenant_id, company_id, invoice_id, provider_id, created_at, updated_at) VALUES (${TENANT_ID}, ${COMPANY_ID}, ${invoice_id_3}, ${provider_id_2}, now(), now()) ON CONFLICT DO NOTHING;" >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Insertar datos de logística con incoterm
|
||||||
|
if [ -n "$invoice_id_1" ]; then
|
||||||
|
exec_pg_sql "INSERT INTO a76.invoice_logistics (tenant_id, company_id, invoice_id, transport_type, weight_type, incoterm, created_at, updated_at) VALUES (${TENANT_ID}, ${COMPANY_ID}, ${invoice_id_1}, 'none', 'kgs', 'FOB', now(), now()) ON CONFLICT DO NOTHING;" >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$invoice_id_2" ]; then
|
||||||
|
exec_pg_sql "INSERT INTO a76.invoice_logistics (tenant_id, company_id, invoice_id, transport_type, weight_type, incoterm, created_at, updated_at) VALUES (${TENANT_ID}, ${COMPANY_ID}, ${invoice_id_2}, 'none', 'kgs', 'CIF', now(), now()) ON CONFLICT DO NOTHING;" >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$invoice_id_3" ]; then
|
||||||
|
exec_pg_sql "INSERT INTO a76.invoice_logistics (tenant_id, company_id, invoice_id, transport_type, weight_type, incoterm, created_at, updated_at) VALUES (${TENANT_ID}, ${COMPANY_ID}, ${invoice_id_3}, 'none', 'kgs', 'EXW', now(), now()) ON CONFLICT DO NOTHING;" >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Ejecutar todas las funciones de seed
|
||||||
|
execute_seed_data() {
|
||||||
|
echo -e "\n${YELLOW}[SEED] Cargando datos de ejemplo...${NC}"
|
||||||
|
|
||||||
|
seed_customs_brokers
|
||||||
|
seed_clients_and_providers
|
||||||
|
seed_packages
|
||||||
|
seed_classes
|
||||||
|
seed_parts
|
||||||
|
seed_pedimentos
|
||||||
|
seed_invoices
|
||||||
|
|
||||||
|
echo -e "${GREEN}✓ Datos de ejemplo cargados exitosamente${NC}"
|
||||||
|
echo -e "${YELLOW} • 3 Agentes aduanales${NC}"
|
||||||
|
echo -e "${YELLOW} • 4 Clientes/Proveedores${NC}"
|
||||||
|
echo -e "${YELLOW} • 4 Tipos de paquete${NC}"
|
||||||
|
echo -e "${YELLOW} • 4 Clases${NC}"
|
||||||
|
echo -e "${YELLOW} • 5 Parts/Componentes${NC}"
|
||||||
|
echo -e "${YELLOW} • 3 Pedimentos${NC}"
|
||||||
|
echo -e "${YELLOW} • 3 Facturas${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
# Variables de configuración
|
# Variables de configuración
|
||||||
KEYCLOAK_URL="${KEYCLOAK_URL:-http://localhost:8080/kcauth}"
|
KEYCLOAK_URL="${KEYCLOAK_URL:-http://localhost:8080/kcauth}"
|
||||||
KEYCLOAK_ADMIN="${KEYCLOAK_ADMIN:-admin}"
|
KEYCLOAK_ADMIN="${KEYCLOAK_ADMIN:-admin}"
|
||||||
@@ -559,8 +745,10 @@ fi
|
|||||||
|
|
||||||
# Obtener información de la company
|
# Obtener información de la company
|
||||||
COMPANY_INFO=$(exec_pg_sql "SELECT id, name FROM a76.company WHERE tenant_id = ${TENANT_ID} LIMIT 1;")
|
COMPANY_INFO=$(exec_pg_sql "SELECT id, name FROM a76.company WHERE tenant_id = ${TENANT_ID} LIMIT 1;")
|
||||||
|
COMPANY_ID=$(exec_pg_sql "SELECT id FROM a76.company WHERE tenant_id = ${TENANT_ID} LIMIT 1;" | xargs)
|
||||||
|
|
||||||
echo -e "${GREEN}✓ Company: ${COMPANY_INFO}${NC}"
|
echo -e "${GREEN}✓ Company: ${COMPANY_INFO}${NC}"
|
||||||
|
echo -e "${GREEN}✓ Company ID: ${COMPANY_ID}${NC}"
|
||||||
|
|
||||||
# Agregar tenant_id al usuario demo en Keycloak
|
# Agregar tenant_id al usuario demo en Keycloak
|
||||||
echo -e "\n${YELLOW}Asignando tenant_id al usuario demo...${NC}"
|
echo -e "\n${YELLOW}Asignando tenant_id al usuario demo...${NC}"
|
||||||
@@ -635,6 +823,13 @@ else
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# 9. Cargar datos de ejemplo (opcional)
|
||||||
|
###############################################################################
|
||||||
|
if [ "$SEED_DATA" = true ]; then
|
||||||
|
execute_seed_data
|
||||||
|
fi
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# Resumen final
|
# Resumen final
|
||||||
###############################################################################
|
###############################################################################
|
||||||
@@ -673,6 +868,18 @@ echo -e " ${GREEN}✓${NC} Plan: Enterprise (ilimitado)"
|
|||||||
echo -e " ${GREEN}✓${NC} Status: Activa"
|
echo -e " ${GREEN}✓${NC} Status: Activa"
|
||||||
echo -e " ${GREEN}✓${NC} Features: API, Reportes Avanzados, Integraciones, Soporte Dedicado"
|
echo -e " ${GREEN}✓${NC} Features: API, Reportes Avanzados, Integraciones, Soporte Dedicado"
|
||||||
echo -e " ${GREEN}✓${NC} Vigencia: 1 año"
|
echo -e " ${GREEN}✓${NC} Vigencia: 1 año"
|
||||||
|
|
||||||
|
if [ "$SEED_DATA" = true ]; then
|
||||||
|
echo -e "\n${YELLOW}Datos de ejemplo:${NC}"
|
||||||
|
echo -e " ${GREEN}✓${NC} Agentes aduanales: 3"
|
||||||
|
echo -e " ${GREEN}✓${NC} Clientes/Proveedores: 4"
|
||||||
|
echo -e " ${GREEN}✓${NC} Tipos de paquete: 4"
|
||||||
|
echo -e " ${GREEN}✓${NC} Clases: 4"
|
||||||
|
echo -e " ${GREEN}✓${NC} Parts/Componentes: 5"
|
||||||
|
echo -e " ${GREEN}✓${NC} Pedimentos: 3"
|
||||||
|
echo -e " ${GREEN}✓${NC} Facturas: 3"
|
||||||
|
fi
|
||||||
|
|
||||||
echo -e "\n${YELLOW}Puedes acceder al sistema en:${NC}"
|
echo -e "\n${YELLOW}Puedes acceder al sistema en:${NC}"
|
||||||
echo -e " ${GREEN}http://localhost:5173${NC}"
|
echo -e " ${GREEN}http://localhost:5173${NC}"
|
||||||
echo -e "\n${GREEN}════════════════════════════════════════════════════════${NC}\n"
|
echo -e "\n${GREEN}════════════════════════════════════════════════════════${NC}\n"
|
||||||
|
|||||||
Reference in New Issue
Block a user