Merge pull request 'feature/pedimentos' (#21) from feature/pedimentos into development
Reviewed-on: ADUANASOFT/anexo76#21
This commit is contained in:
@@ -1,67 +0,0 @@
|
||||
"""Add timestamp fields to classes table
|
||||
|
||||
Revision ID: add_timestamps_classes
|
||||
Revises: 7937209f9718
|
||||
Create Date: 2025-11-16 00:20:00.000000
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = 'add_timestamps_classes'
|
||||
down_revision: Union[str, None] = '7937209f9718'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# Add created_at column with default value
|
||||
op.execute("""
|
||||
ALTER TABLE a76.classes
|
||||
ADD COLUMN created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||
""")
|
||||
|
||||
# Add updated_at column with default value
|
||||
op.execute("""
|
||||
ALTER TABLE a76.classes
|
||||
ADD COLUMN updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||
""")
|
||||
|
||||
# Add deleted_at column (nullable for soft deletes)
|
||||
op.execute("""
|
||||
ALTER TABLE a76.classes
|
||||
ADD COLUMN deleted_at TIMESTAMP WITH TIME ZONE
|
||||
""")
|
||||
|
||||
# Create trigger to auto-update updated_at
|
||||
op.execute("""
|
||||
CREATE OR REPLACE FUNCTION a76.update_classes_updated_at()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = CURRENT_TIMESTAMP;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ language 'plpgsql';
|
||||
""")
|
||||
|
||||
op.execute("""
|
||||
CREATE TRIGGER update_classes_updated_at
|
||||
BEFORE UPDATE ON a76.classes
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION a76.update_classes_updated_at();
|
||||
""")
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# Drop trigger and function
|
||||
op.execute("DROP TRIGGER IF EXISTS update_classes_updated_at ON a76.classes")
|
||||
op.execute("DROP FUNCTION IF EXISTS a76.update_classes_updated_at()")
|
||||
|
||||
# Drop columns
|
||||
op.execute("ALTER TABLE a76.classes DROP COLUMN IF EXISTS deleted_at")
|
||||
op.execute("ALTER TABLE a76.classes DROP COLUMN IF EXISTS updated_at")
|
||||
op.execute("ALTER TABLE a76.classes DROP COLUMN IF EXISTS created_at")
|
||||
@@ -4,7 +4,7 @@ Reemplaza schemas.py siguiendo enfoque DDD y estilo NestJS
|
||||
"""
|
||||
|
||||
from decimal import Decimal
|
||||
from typing import Literal, Optional
|
||||
from typing import List, Literal, Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
@@ -119,7 +119,7 @@ class ClientProviderCreateDTO(BaseModel):
|
||||
is_national_provider: Optional[str] = Field(
|
||||
None, max_length=2, description="Is national provider"
|
||||
)
|
||||
enabled_disabled: Optional[int] = Field(None, description="Enabled/Disabled status")
|
||||
is_active: Optional[bool] = Field(None, description="Enabled/Disabled status")
|
||||
|
||||
# Nested DTOs
|
||||
address: Optional[ClientProviderAddressDTO] = Field(
|
||||
@@ -162,7 +162,7 @@ class ClientProviderUpdateDTO(BaseModel):
|
||||
is_national_provider: Optional[str] = Field(
|
||||
None, max_length=2, description="Is national provider"
|
||||
)
|
||||
enabled_disabled: Optional[int] = Field(None, description="Enabled/Disabled status")
|
||||
is_active: Optional[bool] = Field(None, description="Enabled/Disabled status")
|
||||
|
||||
# Nested DTOs
|
||||
address: Optional[ClientProviderAddressDTO] = Field(
|
||||
@@ -194,7 +194,7 @@ class ClientProviderResponseDTO(BaseModel):
|
||||
position: Optional[str] = None
|
||||
incoterm: Optional[str] = None
|
||||
is_national_provider: Optional[str] = None
|
||||
enabled_disabled: Optional[int] = None
|
||||
is_active: Optional[bool] = None
|
||||
tenant_id: int
|
||||
company_id: int
|
||||
|
||||
@@ -215,7 +215,7 @@ class ClientProviderBasicDTO(BaseModel):
|
||||
short_name: Optional[str] = None
|
||||
rfc: Optional[str] = None
|
||||
client_or_provider: Optional[str] = None
|
||||
enabled_disabled: Optional[int] = None
|
||||
is_active: Optional[bool] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
@@ -231,3 +231,15 @@ class ClientProviderListDTO(BaseModel):
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class ClientProviderPaginatedResponseDTO(BaseModel):
|
||||
"""DTO para respuesta paginada de clientes/proveedores"""
|
||||
|
||||
items: List[ClientProviderResponseDTO]
|
||||
total: int
|
||||
page: int
|
||||
page_size: int
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
@@ -15,7 +15,8 @@ from sqlalchemy import (
|
||||
PrimaryKeyConstraint,
|
||||
SmallInteger,
|
||||
String,
|
||||
Enum as PgEnum
|
||||
Enum as PgEnum,
|
||||
Boolean
|
||||
)
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from enum import Enum
|
||||
@@ -60,8 +61,8 @@ class ClientProvider(Base, TenantScopedMixin):
|
||||
responsible: Mapped[Optional[str]] = mapped_column(String(80))
|
||||
position: Mapped[Optional[str]] = mapped_column(String(30))
|
||||
incoterm: Mapped[Optional[str]] = mapped_column(String(19))
|
||||
is_national_provider: Mapped[Optional[str]] = mapped_column(String(2))
|
||||
enabled_disabled: Mapped[Optional[int]] = mapped_column(SmallInteger)
|
||||
is_national_provider: Mapped[Optional[bool]] = mapped_column(Boolean)
|
||||
is_active: Mapped[Optional[bool]] = mapped_column(Boolean)
|
||||
|
||||
# Relationships
|
||||
address: Mapped[Optional["ClientProviderAddress"]] = relationship(
|
||||
|
||||
@@ -9,136 +9,57 @@ from core.security import get_current_user, validate_access_to_resource
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from api.v1.common.tenant_crud_routes import TenantCRUDRoutes
|
||||
from .models import ClientOrProviderEnum
|
||||
|
||||
from .dto import (
|
||||
ClientProviderBasicDTO,
|
||||
ClientProviderCreateDTO,
|
||||
ClientProviderResponseDTO,
|
||||
ClientProviderUpdateDTO,
|
||||
ClientProviderPaginatedResponseDTO,
|
||||
)
|
||||
from .service import ClientProviderService
|
||||
from .models import ClientProvider
|
||||
|
||||
# Create base CRUD router using TenantCRUDRoutes factory
|
||||
base_router = TenantCRUDRoutes(
|
||||
service=ClientProviderService,
|
||||
create_schema=ClientProviderCreateDTO,
|
||||
update_schema=ClientProviderUpdateDTO,
|
||||
response_schema=ClientProviderResponseDTO,
|
||||
prefix="",
|
||||
tags=[],
|
||||
resource_name="Client/Provider",
|
||||
id_name="id", # Using numeric ID
|
||||
enable_list=True, # Enable GET /clients-providers with pagination
|
||||
enable_filters=True, # Enable filtering
|
||||
default_page_size=50,
|
||||
max_page_size=100,
|
||||
).router
|
||||
|
||||
# Create main router to add custom endpoints
|
||||
router = APIRouter(prefix="/clients-providers")
|
||||
|
||||
# Include base CRUD routes
|
||||
router.include_router(base_router, prefix="")
|
||||
|
||||
|
||||
# Custom endpoints
|
||||
@router.get("/clients", response_model=List[ClientProviderBasicDTO])
|
||||
async def get_clients_only(
|
||||
@router.get("/", response_model=ClientProviderPaginatedResponseDTO)
|
||||
async def get_clients_and_providers(
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
type: Optional[ClientOrProviderEnum] = Query(
|
||||
None, description="Type of entity (client or provider)"
|
||||
),
|
||||
active: Optional[bool] = Query(None, description="Active status"),
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(100, ge=1, le=1000),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""Get only clients (client_or_provider = 'client')"""
|
||||
"""Get clients and providers"""
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
clients = (
|
||||
db.query(ClientProvider)
|
||||
.filter(
|
||||
ClientProvider.tenant_id == tenant_id,
|
||||
ClientProvider.company_id == company_id,
|
||||
ClientProvider.client_or_provider == "client"
|
||||
)
|
||||
.offset(skip)
|
||||
.limit(limit)
|
||||
.all()
|
||||
|
||||
query = db.query(ClientProvider).filter(
|
||||
ClientProvider.tenant_id == tenant_id,
|
||||
ClientProvider.company_id == company_id,
|
||||
)
|
||||
return [ClientProviderBasicDTO.model_validate(c) for c in clients]
|
||||
|
||||
if type is not None:
|
||||
query = query.filter(ClientProvider.client_or_provider == type)
|
||||
|
||||
@router.get("/providers", response_model=List[ClientProviderBasicDTO])
|
||||
async def get_providers_only(
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(100, ge=1, le=1000),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""Get only providers (client_or_provider = 'provider')"""
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
providers = (
|
||||
db.query(ClientProvider)
|
||||
.filter(
|
||||
ClientProvider.tenant_id == tenant_id,
|
||||
ClientProvider.company_id == company_id,
|
||||
ClientProvider.client_or_provider == "provider"
|
||||
)
|
||||
.offset(skip)
|
||||
.limit(limit)
|
||||
.all()
|
||||
)
|
||||
return [ClientProviderBasicDTO.model_validate(p) for p in providers]
|
||||
if active is not None:
|
||||
query = query.filter(ClientProvider.is_active == active)
|
||||
|
||||
total = query.count()
|
||||
clients = query.offset(skip).limit(limit).all()
|
||||
|
||||
@router.get("/search/rfc/{rfc}", response_model=List[ClientProviderBasicDTO])
|
||||
async def search_by_rfc(
|
||||
rfc: str,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""Search clients/providers by RFC"""
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
clients = (
|
||||
db.query(ClientProvider)
|
||||
.filter(
|
||||
ClientProvider.tenant_id == tenant_id,
|
||||
ClientProvider.company_id == company_id,
|
||||
ClientProvider.rfc.ilike(f"%{rfc}%")
|
||||
)
|
||||
.all()
|
||||
)
|
||||
return [ClientProviderBasicDTO.model_validate(c) for c in clients]
|
||||
|
||||
|
||||
@router.patch("/{client_id}/toggle-status", response_model=ClientProviderResponseDTO)
|
||||
async def toggle_clients_and_providers_status(
|
||||
client_id: int,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""Toggle client/provider enabled/disabled status"""
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
client = ClientProviderService.get_by_id(db, client_id, tenant_id, company_id)
|
||||
if not client:
|
||||
raise HTTPException(status_code=404, detail="Client/Provider not found")
|
||||
|
||||
# Toggle status (1 = enabled, 0 = disabled)
|
||||
client.enabled_disabled = 1 if client.enabled_disabled == 0 else 0
|
||||
|
||||
try:
|
||||
db.commit()
|
||||
db.refresh(client)
|
||||
return client
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
raise HTTPException(status_code=500, detail="Error updating status")
|
||||
return {
|
||||
"items": [ClientProviderResponseDTO.model_validate(c) for c in clients],
|
||||
"total": total,
|
||||
"page": (skip // limit) + 1,
|
||||
"page_size": limit,
|
||||
}
|
||||
|
||||
|
||||
@router.get("/{client_id}/basic", response_model=ClientProviderBasicDTO)
|
||||
@@ -150,9 +71,59 @@ async def get_clients_and_providers_basic_info(
|
||||
):
|
||||
"""Get basic information for a client/provider (without address and programs)"""
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
|
||||
client = ClientProviderService.get_by_id(db, client_id, tenant_id, company_id)
|
||||
if not client:
|
||||
raise HTTPException(status_code=404, detail="Client/Provider not found")
|
||||
|
||||
|
||||
return ClientProviderBasicDTO.model_validate(client)
|
||||
|
||||
|
||||
@router.post("/", response_model=ClientProviderResponseDTO)
|
||||
async def create_client_provider(
|
||||
client_data: ClientProviderCreateDTO,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""Create a new client/provider"""
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
return ClientProviderService.create(db, client_data, tenant_id, company_id)
|
||||
|
||||
|
||||
@router.patch("/{client_id}", response_model=ClientProviderResponseDTO)
|
||||
async def update_client_provider(
|
||||
client_id: int,
|
||||
client_data: ClientProviderUpdateDTO,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""Update a client/provider"""
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
client = ClientProviderService.update(
|
||||
db, client_id, tenant_id, company_id, client_data
|
||||
)
|
||||
if not client:
|
||||
raise HTTPException(status_code=404, detail="Client/Provider not found")
|
||||
|
||||
return client
|
||||
|
||||
|
||||
@router.delete("/{client_id}", response_model=bool)
|
||||
async def delete_client_provider(
|
||||
client_id: int,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""Delete a client/provider"""
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
success = ClientProviderService.delete(db, client_id, tenant_id, company_id)
|
||||
if not success:
|
||||
raise HTTPException(status_code=404, detail="Client/Provider not found")
|
||||
|
||||
return success
|
||||
|
||||
@@ -61,13 +61,17 @@ class ClientProviderService:
|
||||
)
|
||||
if filters.get("status"):
|
||||
enabled = 1 if filters["status"] == "enabled" else 0
|
||||
query = query.filter(ClientProvider.enabled_disabled == enabled)
|
||||
query = query.filter(ClientProvider.is_active == enabled)
|
||||
|
||||
total = query.count()
|
||||
clients = query.options(
|
||||
joinedload(ClientProvider.address),
|
||||
joinedload(ClientProvider.programs)
|
||||
).offset(skip).limit(limit).all()
|
||||
clients = (
|
||||
query.options(
|
||||
joinedload(ClientProvider.address), joinedload(ClientProvider.programs)
|
||||
)
|
||||
.offset(skip)
|
||||
.limit(limit)
|
||||
.all()
|
||||
)
|
||||
|
||||
return clients, total
|
||||
|
||||
@@ -79,8 +83,7 @@ class ClientProviderService:
|
||||
return (
|
||||
db.query(ClientProvider)
|
||||
.options(
|
||||
joinedload(ClientProvider.address),
|
||||
joinedload(ClientProvider.programs)
|
||||
joinedload(ClientProvider.address), joinedload(ClientProvider.programs)
|
||||
)
|
||||
.filter(
|
||||
ClientProvider.id == client_id,
|
||||
@@ -102,9 +105,7 @@ class ClientProviderService:
|
||||
# Create main client/provider
|
||||
data_dict = client_data.model_dump(exclude={"address", "programs"})
|
||||
db_client = ClientProvider(
|
||||
**data_dict,
|
||||
tenant_id=tenant_id,
|
||||
company_id=company_id
|
||||
**data_dict, tenant_id=tenant_id, company_id=company_id
|
||||
)
|
||||
|
||||
db.add(db_client)
|
||||
@@ -112,8 +113,8 @@ class ClientProviderService:
|
||||
|
||||
# Create address if provided
|
||||
if client_data.address:
|
||||
db_address = ClientProviderAddress(
|
||||
tenant_id=tenant_id,
|
||||
db_address = ClientProviderAddress(
|
||||
tenant_id=tenant_id,
|
||||
company_id=company_id,
|
||||
client_id=db_client.id,
|
||||
**client_data.address.model_dump(exclude_unset=True),
|
||||
@@ -122,7 +123,7 @@ class ClientProviderService:
|
||||
|
||||
# Create programs if provided
|
||||
if client_data.programs:
|
||||
db_programs = ClientProviderPrograms(
|
||||
db_programs = ClientProviderPrograms(
|
||||
tenant_id=tenant_id,
|
||||
company_id=company_id,
|
||||
client_id=db_client.id,
|
||||
@@ -210,9 +211,7 @@ class ClientProviderService:
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def delete(
|
||||
db: Session, client_id: int, tenant_id: int, company_id: int
|
||||
) -> bool:
|
||||
def delete(db: Session, client_id: int, tenant_id: int, company_id: int) -> bool:
|
||||
"""Delete a client/provider"""
|
||||
client = ClientProviderService.get_by_id(db, client_id, tenant_id, company_id)
|
||||
if not client:
|
||||
@@ -351,7 +350,7 @@ class ClientProviderService:
|
||||
)
|
||||
|
||||
if enabled_only:
|
||||
query = query.filter(ClientProvider.enabled_disabled == 1)
|
||||
query = query.filter(ClientProvider.is_active == 1)
|
||||
|
||||
# Contar total
|
||||
total = query.count()
|
||||
@@ -478,59 +477,4 @@ class ClientProviderService:
|
||||
logger.error(f"Error deleting client/provider {client_id}: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=500, detail="Error deleting client/provider"
|
||||
)
|
||||
|
||||
def get_clients_only(
|
||||
self, skip: int = 0, limit: int = 100
|
||||
) -> List[ClientProviderBasicDTO]:
|
||||
"""Obtiene solo clientes (client)"""
|
||||
query = self.db.query(ClientProvider).filter(
|
||||
ClientProvider.client_or_provider == "client"
|
||||
)
|
||||
clients = query.offset(skip).limit(limit).all()
|
||||
return [ClientProviderBasicDTO.model_validate(client) for client in clients]
|
||||
|
||||
def get_providers_only(
|
||||
self, skip: int = 0, limit: int = 100
|
||||
) -> List[ClientProviderBasicDTO]:
|
||||
"""Obtiene solo proveedores (provider)"""
|
||||
query = self.db.query(ClientProvider).filter(
|
||||
ClientProvider.client_or_provider == "provider"
|
||||
)
|
||||
providers = query.offset(skip).limit(limit).all()
|
||||
return [
|
||||
ClientProviderBasicDTO.model_validate(provider) for provider in providers
|
||||
]
|
||||
|
||||
def search_by_rfc(self, rfc: str) -> List[ClientProviderBasicDTO]:
|
||||
"""Busca clientes/proveedores por RFC"""
|
||||
clients = (
|
||||
self.db.query(ClientProvider)
|
||||
.filter(ClientProvider.rfc.ilike(f"%{rfc}%"))
|
||||
.all()
|
||||
)
|
||||
return [ClientProviderBasicDTO.model_validate(client) for client in clients]
|
||||
|
||||
def toggle_status(self, client_id: str) -> Optional[ClientProviderResponseDTO]:
|
||||
"""Cambia el estado habilitado/deshabilitado"""
|
||||
client = (
|
||||
self.db.query(ClientProvider)
|
||||
.filter(ClientProvider.client_id == client_id)
|
||||
.first()
|
||||
)
|
||||
if not client:
|
||||
return None
|
||||
|
||||
# Toggle status (1 = habilitado, 0 = deshabilitado)
|
||||
client.enabled_disabled = 1 if client.enabled_disabled == 0 else 0
|
||||
|
||||
try:
|
||||
self.db.commit()
|
||||
logger.info(
|
||||
f"Client/Provider status toggled: {client_id} -> {client.enabled_disabled}"
|
||||
)
|
||||
return self._get_client_with_relations(client_id)
|
||||
except Exception as e:
|
||||
self.db.rollback()
|
||||
logger.error(f"Error toggling status for {client_id}: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Error updating status")
|
||||
)
|
||||
@@ -67,7 +67,7 @@ class PartCreateDTO(BaseModel):
|
||||
added_value: Optional[Decimal] = Field(None, description="Added value")
|
||||
|
||||
# Status and media
|
||||
enabled_disabled: Optional[int] = Field(None, description="Enabled/Disabled status")
|
||||
is_active: Optional[bool] = Field(None, description="Enabled/Disabled status")
|
||||
creation_date: Optional[int] = Field(None, description="Creation date")
|
||||
part_photo: Optional[str] = Field(
|
||||
None, max_length=255, description="Part photo URL"
|
||||
@@ -132,7 +132,7 @@ class PartUpdateDTO(BaseModel):
|
||||
added_value: Optional[Decimal] = Field(None, description="Added value")
|
||||
|
||||
# Status and media
|
||||
enabled_disabled: Optional[int] = Field(None, description="Enabled/Disabled status")
|
||||
is_active: Optional[bool] = Field(None, description="Enabled/Disabled status")
|
||||
part_photo: Optional[str] = Field(
|
||||
None, max_length=255, description="Part photo URL"
|
||||
)
|
||||
@@ -178,7 +178,7 @@ class PartResponseDTO(BaseModel):
|
||||
added_value: Optional[Decimal] = None
|
||||
|
||||
# Status and dates
|
||||
enabled_disabled: Optional[int] = None
|
||||
is_active: Optional[bool] = None
|
||||
creation_date: Optional[int] = None
|
||||
modification_date: Optional[int] = None
|
||||
modification_date_iso: Optional[datetime] = None
|
||||
@@ -200,7 +200,7 @@ class PartBasicDTO(BaseModel):
|
||||
part_class: Optional[str] = None
|
||||
unit_cost: Optional[Decimal] = None
|
||||
currency_key: Optional[str] = None
|
||||
enabled_disabled: Optional[int] = None
|
||||
is_active: Optional[bool] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
@@ -16,6 +16,7 @@ from sqlalchemy import (
|
||||
SmallInteger,
|
||||
String,
|
||||
UniqueConstraint,
|
||||
Boolean,
|
||||
)
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
@@ -90,7 +91,7 @@ class Part(Base, TenantScopedMixin):
|
||||
added_value: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8))
|
||||
|
||||
# Status and dates
|
||||
enabled_disabled: Mapped[Optional[int]] = mapped_column(SmallInteger)
|
||||
is_active: Mapped[Optional[bool]] = mapped_column(Boolean)
|
||||
creation_date: Mapped[Optional[int]] = mapped_column() # FECHACREACIONPARTE
|
||||
modification_date: Mapped[Optional[int]] = mapped_column() # FECHAMODIFICA
|
||||
modification_date_iso: Mapped[Optional[datetime]] = (
|
||||
|
||||
@@ -348,7 +348,7 @@ async def get_part_basic_info(
|
||||
part_class=part.part_class,
|
||||
unit_cost=part.unit_cost,
|
||||
currency_key=part.currency_key,
|
||||
enabled_disabled=part.enabled_disabled,
|
||||
is_active=part.is_active,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -221,7 +221,7 @@ class PartService:
|
||||
return None
|
||||
|
||||
# Toggle status (assuming 1 = enabled, 0 = disabled)
|
||||
db_part.enabled_disabled = 1 if db_part.enabled_disabled == 0 else 0
|
||||
db_part.is_active = 1 if db_part.is_active == 0 else 0
|
||||
|
||||
db.commit()
|
||||
db.refresh(db_part)
|
||||
@@ -257,8 +257,8 @@ class PartService:
|
||||
)
|
||||
|
||||
# Partes habilitadas vs deshabilitadas
|
||||
enabled_parts = db.query(Part).filter(Part.enabled_disabled == 1).count()
|
||||
disabled_parts = db.query(Part).filter(Part.enabled_disabled == 0).count()
|
||||
enabled_parts = db.query(Part).filter(Part.is_active == 1).count()
|
||||
disabled_parts = db.query(Part).filter(Part.is_active == 0).count()
|
||||
|
||||
return {
|
||||
"total_parts": total_parts,
|
||||
|
||||
@@ -7,9 +7,8 @@ from pydantic import BaseModel, ConfigDict, Field
|
||||
class PedimentoDatesBase(BaseModel):
|
||||
"""Base schema for Pedimento Dates"""
|
||||
|
||||
entry_date: Optional[datetime] = Field(None, description="Entry date")
|
||||
pedimento_date: Optional[datetime] = Field(None, description="Pedimento date")
|
||||
payment_date: Optional[datetime] = Field(None, description="Payment date")
|
||||
entry_date: Optional[datetime] = Field(None, description="Entry date")
|
||||
payment_date: datetime = Field(None, description="Payment date")
|
||||
rectification_payment_date: Optional[datetime] = Field(
|
||||
None, description="Rectification payment date"
|
||||
)
|
||||
@@ -19,21 +18,26 @@ class PedimentoDatesBase(BaseModel):
|
||||
original_date: Optional[datetime] = Field(None, description="Original date")
|
||||
start_date: Optional[datetime] = Field(None, description="Start date")
|
||||
end_date: Optional[datetime] = Field(None, description="End date")
|
||||
capture_date: Optional[datetime] = Field(None, description="Capture date")
|
||||
capture_time: Optional[time] = Field(None, description="Capture time")
|
||||
|
||||
|
||||
class PedimentoDatesCreate(PedimentoDatesBase):
|
||||
"""Schema for creating a new Pedimento Dates"""
|
||||
class PedimentoDatesCreate(BaseModel):
|
||||
"""Schema for creating a new Pedimento Dates - pedimento_id and tenant_id are set by backend"""
|
||||
|
||||
pass
|
||||
entry_date: Optional[datetime] = Field(None, description="Entry date")
|
||||
payment_date: datetime = Field(..., description="Payment date")
|
||||
rectification_payment_date: Optional[datetime] = Field(None, description="Rectification payment date")
|
||||
extraction_date: Optional[datetime] = Field(None, description="Extraction date")
|
||||
submission_date: Optional[datetime] = Field(None, description="Submission date")
|
||||
eucan_date: Optional[datetime] = Field(None, description="EUCAN date")
|
||||
original_date: Optional[datetime] = Field(None, description="Original date")
|
||||
start_date: Optional[datetime] = Field(None, description="Start date")
|
||||
end_date: Optional[datetime] = Field(None, description="End date")
|
||||
|
||||
|
||||
class PedimentoDatesUpdate(BaseModel):
|
||||
"""Schema for updating a Pedimento Dates"""
|
||||
|
||||
entry_date: Optional[datetime] = None
|
||||
pedimento_date: Optional[datetime] = None
|
||||
entry_date: Optional[datetime] = None
|
||||
payment_date: Optional[datetime] = None
|
||||
rectification_payment_date: Optional[datetime] = None
|
||||
extraction_date: Optional[datetime] = None
|
||||
@@ -42,8 +46,6 @@ class PedimentoDatesUpdate(BaseModel):
|
||||
original_date: Optional[datetime] = None
|
||||
start_date: Optional[datetime] = None
|
||||
end_date: Optional[datetime] = None
|
||||
capture_date: Optional[datetime] = None
|
||||
capture_time: Optional[time] = None
|
||||
|
||||
|
||||
class PedimentoDatesResponse(PedimentoDatesBase):
|
||||
|
||||
@@ -25,14 +25,23 @@ class PedimentoPaymentsBase(BaseModel):
|
||||
total_cash_paid: Optional[int] = Field(None, description="Total cash paid")
|
||||
total_contributions: Optional[int] = Field(None, description="Total contributions")
|
||||
counter_payment: Optional[int] = Field(None, description="Counter payment")
|
||||
pece_code: Optional[str] = Field(None, max_length=5, description="PECE code")
|
||||
payment_id: Optional[int] = Field(None, description="Payment ID")
|
||||
pece_code: Optional[str] = Field(None, max_length=5, description="PECE code")
|
||||
|
||||
|
||||
class PedimentoPaymentsCreate(PedimentoPaymentsBase):
|
||||
"""Schema for creating a new Pedimento Payments"""
|
||||
class PedimentoPaymentsCreate(BaseModel):
|
||||
"""Schema for creating a new Pedimento Payments - pedimento_id and tenant_id are set by backend"""
|
||||
|
||||
pass
|
||||
acknowledgment: Optional[str] = Field(None, max_length=20, description="Acknowledgment")
|
||||
operation_number: Optional[str] = Field(None, max_length=14, description="Operation number")
|
||||
bank_code: Optional[int] = Field(None, description="Bank code")
|
||||
cashier: Optional[str] = Field(None, max_length=2, description="Cashier")
|
||||
date: Optional[Date] = Field(None, description="Date")
|
||||
time: Optional[Time] = Field(None, description="Time")
|
||||
shift: Optional[str] = Field(None, max_length=1, description="Shift")
|
||||
total_cash_paid: Optional[int] = Field(None, description="Total cash paid")
|
||||
total_contributions: Optional[int] = Field(None, description="Total contributions")
|
||||
counter_payment: Optional[int] = Field(None, description="Counter payment")
|
||||
pece_code: Optional[str] = Field(None, max_length=5, description="PECE code")
|
||||
|
||||
|
||||
class PedimentoPaymentsUpdate(BaseModel):
|
||||
@@ -48,8 +57,7 @@ class PedimentoPaymentsUpdate(BaseModel):
|
||||
total_cash_paid: Optional[int] = None
|
||||
total_contributions: Optional[int] = None
|
||||
counter_payment: Optional[int] = None
|
||||
pece_code: Optional[str] = Field(None, max_length=5)
|
||||
payment_id: Optional[int] = None
|
||||
pece_code: Optional[str] = Field(None, max_length=5)
|
||||
|
||||
|
||||
class PedimentoPaymentsResponse(PedimentoPaymentsBase):
|
||||
|
||||
@@ -15,10 +15,13 @@ class PedimentoTransportMeansBase(BaseModel):
|
||||
departure: Optional[str] = Field(None, max_length=2, description="Departure")
|
||||
|
||||
|
||||
class PedimentoTransportMeansCreate(PedimentoTransportMeansBase):
|
||||
"""Schema for creating a new Pedimento Transport Means"""
|
||||
class PedimentoTransportMeansCreate(BaseModel):
|
||||
"""Schema for creating a new Pedimento Transport Means - pedimento_id and tenant_id are set by backend"""
|
||||
|
||||
pass
|
||||
destination: Optional[int] = Field(None, description="Destination")
|
||||
entry_exit: Optional[str] = Field(None, max_length=2, description="Entry/exit")
|
||||
arrival: Optional[str] = Field(None, max_length=2, description="Arrival")
|
||||
departure: Optional[str] = Field(None, max_length=2, description="Departure")
|
||||
|
||||
|
||||
class PedimentoTransportMeansUpdate(BaseModel):
|
||||
|
||||
@@ -27,10 +27,17 @@ class PedimentoValidationBase(BaseModel):
|
||||
responsible_id: Optional[int] = Field(None, description="Responsible ID")
|
||||
|
||||
|
||||
class PedimentoValidationCreate(PedimentoValidationBase):
|
||||
"""Schema for creating a new Pedimento Validation"""
|
||||
class PedimentoValidationCreate(BaseModel):
|
||||
"""Schema for creating a new Pedimento Validation - pedimento_id and tenant_id are set by backend"""
|
||||
|
||||
pass
|
||||
validator: Optional[str] = Field(None, max_length=3, description="Validator")
|
||||
validation_ack: Optional[str] = Field(None, max_length=8, description="Validation acknowledgment")
|
||||
pre_ack: Optional[str] = Field(None, max_length=8, description="Previous acknowledgment")
|
||||
line_signature: Optional[str] = Field(None, max_length=50, description="Line signature")
|
||||
electronic_signature: Optional[str] = Field(None, max_length=999, description="Electronic signature")
|
||||
certificate_number: Optional[str] = Field(None, max_length=99, description="Certificate number")
|
||||
validator_id: Optional[int] = Field(None, description="Validator ID")
|
||||
responsible_id: Optional[int] = Field(None, description="Responsible ID")
|
||||
|
||||
|
||||
class PedimentoValidationUpdate(BaseModel):
|
||||
|
||||
@@ -5,22 +5,22 @@ from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from .pedimento_config_additional import PedimentoConfigAdditionalCreate
|
||||
from .pedimento_config_calculations import PedimentoConfigCalculationsCreate
|
||||
from .pedimento_config_parameters import PedimentoConfigParametersCreate
|
||||
from .pedimento_config_surcharges import PedimentoConfigSurchargesCreate
|
||||
from .pedimento_config_update_rectification import PedimentoConfigUpdateRectificationCreate
|
||||
from .pedimento_config_updates import PedimentoConfigUpdatesCreate
|
||||
from .pedimento_customs_offices import PedimentoCustomsOfficesCreate
|
||||
from .pedimento_dates import PedimentoDatesCreate
|
||||
from .pedimento_decrementables import PedimentoDecrementablesCreate
|
||||
from .pedimento_incrementables import PedimentoIncrementablesCreate
|
||||
from .pedimento_indexes import PedimentoIndexesCreate
|
||||
from .pedimento_payments import PedimentoPaymentsCreate
|
||||
from .pedimento_rectification_destination import PedimentoRectificationDestinationCreate
|
||||
from .pedimento_rectification_origin import PedimentoRectificationOriginCreate
|
||||
from .pedimento_transport_means import PedimentoTransportMeansCreate
|
||||
from .pedimento_validation import PedimentoValidationCreate
|
||||
from .pedimento_config_additional import PedimentoConfigAdditionalCreate, PedimentoConfigAdditionalResponse
|
||||
from .pedimento_config_calculations import PedimentoConfigCalculationsCreate, PedimentoConfigCalculationsResponse
|
||||
from .pedimento_config_parameters import PedimentoConfigParametersCreate, PedimentoConfigParametersResponse
|
||||
from .pedimento_config_surcharges import PedimentoConfigSurchargesCreate, PedimentoConfigSurchargesResponse
|
||||
from .pedimento_config_update_rectification import PedimentoConfigUpdateRectificationCreate, PedimentoConfigUpdateRectificationResponse
|
||||
from .pedimento_config_updates import PedimentoConfigUpdatesCreate, PedimentoConfigUpdatesResponse
|
||||
from .pedimento_customs_offices import PedimentoCustomsOfficesCreate, PedimentoCustomsOfficesResponse
|
||||
from .pedimento_dates import PedimentoDatesCreate, PedimentoDatesResponse
|
||||
from .pedimento_decrementables import PedimentoDecrementablesCreate, PedimentoDecrementablesResponse
|
||||
from .pedimento_incrementables import PedimentoIncrementablesCreate, PedimentoIncrementablesResponse
|
||||
from .pedimento_indexes import PedimentoIndexesCreate, PedimentoIndexesResponse
|
||||
from .pedimento_payments import PedimentoPaymentsCreate, PedimentoPaymentsResponse
|
||||
from .pedimento_rectification_destination import PedimentoRectificationDestinationCreate, PedimentoRectificationDestinationResponse
|
||||
from .pedimento_rectification_origin import PedimentoRectificationOriginCreate, PedimentoRectificationOriginResponse
|
||||
from .pedimento_transport_means import PedimentoTransportMeansCreate, PedimentoTransportMeansResponse
|
||||
from .pedimento_validation import PedimentoValidationCreate, PedimentoValidationResponse
|
||||
|
||||
|
||||
class OperationType(IntEnum):
|
||||
@@ -50,8 +50,22 @@ class PedimentosBase(BaseModel):
|
||||
usd_value: Optional[Decimal] = Field(None, description="USD value")
|
||||
paid_price: Optional[Decimal] = Field(None, description="Paid price")
|
||||
gross_weight: Optional[Decimal] = Field(None, description="Gross weight")
|
||||
exchange_rate: Optional[Decimal] = Field(None, description="Exchange rate")
|
||||
exchange_rate: Optional[Decimal] = Field(None, description="Exchange rate")
|
||||
|
||||
class PedimentosCreate(PedimentosBase):
|
||||
"""Schema for creating a new Pedimento"""
|
||||
|
||||
# Override to make required fields non-optional
|
||||
year: str = Field(..., max_length=2, description="Year")
|
||||
customs_office: str = Field(..., max_length=2, description="Customs office")
|
||||
license: str = Field(..., max_length=4, description="License")
|
||||
pedimento_number: str = Field(..., max_length=7, description="Pedimento number")
|
||||
client_id: int = Field(..., description="Client ID")
|
||||
operation_type: int = Field(..., description="Operation type")
|
||||
pedimento_type: int = Field(..., description="Pedimento type")
|
||||
regime: str = Field(..., max_length=3, description="Regime")
|
||||
status: str = Field(..., max_length=30, description="Status")
|
||||
|
||||
pedimento_dates: Optional[PedimentoDatesCreate] = None
|
||||
pedimento_decrementables: Optional[PedimentoDecrementablesCreate] = None
|
||||
pedimento_incrementables: Optional[PedimentoIncrementablesCreate] = None
|
||||
@@ -69,37 +83,41 @@ class PedimentosBase(BaseModel):
|
||||
pedimento_config_update_rectification: Optional[PedimentoConfigUpdateRectificationCreate] = None
|
||||
pedimento_config_updates: Optional[PedimentoConfigUpdatesCreate] = None
|
||||
|
||||
class PedimentosCreate(PedimentosBase):
|
||||
"""Schema for creating a new Pedimento"""
|
||||
|
||||
# Override to make required fields non-optional
|
||||
year: str = Field(..., max_length=2, description="Year")
|
||||
customs_office: str = Field(..., max_length=2, description="Customs office")
|
||||
license: str = Field(..., max_length=4, description="License")
|
||||
pedimento_number: str = Field(..., max_length=7, description="Pedimento number")
|
||||
client_id: int = Field(..., description="Client ID")
|
||||
operation_type: int = Field(..., description="Operation type")
|
||||
pedimento_type: int = Field(..., description="Pedimento type")
|
||||
regime: str = Field(..., max_length=3, description="Regime")
|
||||
status: str = Field(..., max_length=30, description="Status")
|
||||
|
||||
class PedimentosUpdate(BaseModel):
|
||||
"""Schema for updating a Pedimento"""
|
||||
|
||||
year: Optional[str] = Field(..., max_length=2)
|
||||
customs_office: Optional[str] = Field(..., max_length=2)
|
||||
license: Optional[str] = Field(..., max_length=4)
|
||||
pedimento_number: Optional[str] = Field(..., max_length=7)
|
||||
client_id: Optional[int]
|
||||
operation_type: Optional[OperationType]
|
||||
pedimento_type: Optional[int]
|
||||
pedimento_code: Optional[str] = Field(..., max_length=2)
|
||||
regime: Optional[str] = Field(..., max_length=3)
|
||||
status: Optional[str] = Field(..., max_length=30)
|
||||
year: Optional[str] = Field(None, max_length=2)
|
||||
customs_office: Optional[str] = Field(None, max_length=2)
|
||||
license: Optional[str] = Field(None, max_length=4)
|
||||
pedimento_number: Optional[str] = Field(None, max_length=7)
|
||||
client_id: Optional[int] = None
|
||||
operation_type: Optional[int] = None
|
||||
pedimento_type: Optional[int] = None
|
||||
pedimento_code: Optional[str] = Field(None, max_length=2)
|
||||
regime: Optional[str] = Field(None, max_length=3)
|
||||
status: Optional[str] = Field(None, max_length=30)
|
||||
usd_value: Optional[Decimal] = None
|
||||
paid_price: Optional[Decimal] = None
|
||||
gross_weight: Optional[Decimal] = None
|
||||
exchange_rate: Optional[Decimal] = None
|
||||
|
||||
# Sub-resources
|
||||
pedimento_dates: Optional[PedimentoDatesCreate] = None
|
||||
pedimento_decrementables: Optional[PedimentoDecrementablesCreate] = None
|
||||
pedimento_incrementables: Optional[PedimentoIncrementablesCreate] = None
|
||||
pedimento_indexes: Optional[PedimentoIndexesCreate] = None
|
||||
pedimento_validation: Optional[PedimentoValidationCreate] = None
|
||||
pedimento_customs_offices: Optional[PedimentoCustomsOfficesCreate] = None
|
||||
pedimento_payments: Optional[PedimentoPaymentsCreate] = None
|
||||
pedimento_rectification_destination: Optional[PedimentoRectificationDestinationCreate] = None
|
||||
pedimento_rectification_origin: Optional[PedimentoRectificationOriginCreate] = None
|
||||
pedimento_transport_means: Optional[PedimentoTransportMeansCreate] = None
|
||||
pedimento_config_additional: Optional[PedimentoConfigAdditionalCreate] = None
|
||||
pedimento_config_calculations: Optional[PedimentoConfigCalculationsCreate] = None
|
||||
pedimento_config_parameters: Optional[PedimentoConfigParametersCreate] = None
|
||||
pedimento_config_surcharges: Optional[PedimentoConfigSurchargesCreate] = None
|
||||
pedimento_config_update_rectification: Optional[PedimentoConfigUpdateRectificationCreate] = None
|
||||
pedimento_config_updates: Optional[PedimentoConfigUpdatesCreate] = None
|
||||
|
||||
|
||||
class PedimentosResponse(PedimentosBase):
|
||||
@@ -108,5 +126,22 @@ class PedimentosResponse(PedimentosBase):
|
||||
id: int
|
||||
tenant_id: int
|
||||
created_at: datetime
|
||||
|
||||
pedimento_dates: Optional[PedimentoDatesResponse] = None
|
||||
pedimento_decrementables: Optional[PedimentoDecrementablesResponse] = None
|
||||
pedimento_incrementables: Optional[PedimentoIncrementablesResponse] = None
|
||||
pedimento_indexes: Optional[PedimentoIndexesResponse] = None
|
||||
pedimento_validation: Optional[PedimentoValidationResponse] = None
|
||||
pedimento_customs_offices: Optional[PedimentoCustomsOfficesResponse] = None
|
||||
pedimento_payments: Optional[PedimentoPaymentsResponse] = None
|
||||
pedimento_rectification_destination: Optional[PedimentoRectificationDestinationResponse] = None
|
||||
pedimento_rectification_origin: Optional[PedimentoRectificationOriginResponse] = None
|
||||
pedimento_transport_means: Optional[PedimentoTransportMeansResponse] = None
|
||||
pedimento_config_additional: Optional[PedimentoConfigAdditionalResponse] = None
|
||||
pedimento_config_calculations: Optional[PedimentoConfigCalculationsResponse] = None
|
||||
pedimento_config_parameters: Optional[PedimentoConfigParametersResponse] = None
|
||||
pedimento_config_surcharges: Optional[PedimentoConfigSurchargesResponse] = None
|
||||
pedimento_config_update_rectification: Optional[PedimentoConfigUpdateRectificationResponse] = None
|
||||
pedimento_config_updates: Optional[PedimentoConfigUpdatesResponse] = None
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from datetime import datetime
|
||||
from datetime import time as datetime_time
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from api.v1.common.base_models import TenantScopedMixin, TimestampMixin
|
||||
from core.database import Base
|
||||
@@ -48,16 +48,16 @@ class PedimentoDates(Base, TenantScopedMixin, TimestampMixin):
|
||||
id: Mapped[int] = mapped_column(Integer)
|
||||
pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
|
||||
entry_date: Mapped[datetime] = mapped_column(DateTime)
|
||||
entry_date: Mapped[Optional[datetime]] = mapped_column(DateTime)
|
||||
pedimento_date: Mapped[datetime] = mapped_column(DateTime)
|
||||
payment_date: Mapped[datetime] = mapped_column(DateTime)
|
||||
rectification_payment_date: Mapped[datetime] = mapped_column(DateTime)
|
||||
extraction_date: Mapped[datetime] = mapped_column(DateTime)
|
||||
submission_date: Mapped[datetime] = mapped_column(DateTime)
|
||||
eucan_date: Mapped[datetime] = mapped_column(DateTime)
|
||||
original_date: Mapped[datetime] = mapped_column(DateTime)
|
||||
start_date: Mapped[datetime] = mapped_column(DateTime)
|
||||
end_date: Mapped[datetime] = mapped_column(DateTime)
|
||||
rectification_payment_date: Mapped[Optional[datetime]] = mapped_column(DateTime)
|
||||
extraction_date: Mapped[Optional[datetime]] = mapped_column(DateTime)
|
||||
submission_date: Mapped[Optional[datetime]] = mapped_column(DateTime)
|
||||
eucan_date: Mapped[Optional[datetime]] = mapped_column(DateTime)
|
||||
original_date: Mapped[Optional[datetime]] = mapped_column(DateTime)
|
||||
start_date: Mapped[Optional[datetime]] = mapped_column(DateTime)
|
||||
end_date: Mapped[Optional[datetime]] = mapped_column(DateTime)
|
||||
capture_date: Mapped[datetime] = mapped_column(DateTime)
|
||||
capture_time: Mapped[datetime_time] = mapped_column(Time)
|
||||
|
||||
|
||||
@@ -49,7 +49,6 @@ class PedimentoPayments(Base, TenantScopedMixin, TimestampMixin):
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer)
|
||||
pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
payment_id: Mapped[int] = mapped_column(Integer)
|
||||
|
||||
acknowledgment: Mapped[str] = mapped_column(String(20))
|
||||
operation_number: Mapped[str] = mapped_column(String(14))
|
||||
|
||||
@@ -35,10 +35,10 @@ async def list_payments(
|
||||
return payments
|
||||
|
||||
|
||||
@router.get("/{payment_id}", response_model=PedimentoPaymentsResponse)
|
||||
@router.get("/{id}", response_model=PedimentoPaymentsResponse)
|
||||
async def get_payment(
|
||||
pedimento_id: int,
|
||||
payment_id: int,
|
||||
id: int,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
@@ -47,7 +47,7 @@ async def get_payment(
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
payment = PedimentoPaymentsService.get_by_id(
|
||||
db, payment_id, pedimento_id, tenant_id, company_id
|
||||
db, id, pedimento_id, tenant_id, company_id
|
||||
)
|
||||
if not payment:
|
||||
raise HTTPException(status_code=404, detail="Payment not found")
|
||||
@@ -74,10 +74,10 @@ async def create_payment(
|
||||
return payment
|
||||
|
||||
|
||||
@router.put("/{payment_id}", response_model=PedimentoPaymentsResponse)
|
||||
@router.put("/{id}", response_model=PedimentoPaymentsResponse)
|
||||
async def update_payment(
|
||||
pedimento_id: int,
|
||||
payment_id: int,
|
||||
id: int,
|
||||
data: PedimentoPaymentsUpdate,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
@@ -87,7 +87,7 @@ async def update_payment(
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
payment = PedimentoPaymentsService.update(
|
||||
db, payment_id, pedimento_id, tenant_id, company_id, data
|
||||
db, id, pedimento_id, tenant_id, company_id, data
|
||||
)
|
||||
if not payment:
|
||||
raise HTTPException(status_code=404, detail="Payment not found")
|
||||
@@ -95,10 +95,10 @@ async def update_payment(
|
||||
return payment
|
||||
|
||||
|
||||
@router.delete("/{payment_id}", status_code=204)
|
||||
@router.delete("/{id}", status_code=204)
|
||||
async def delete_payment(
|
||||
pedimento_id: int,
|
||||
payment_id: int,
|
||||
id: int,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
@@ -107,7 +107,7 @@ async def delete_payment(
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
success = PedimentoPaymentsService.delete(
|
||||
db, payment_id, pedimento_id, tenant_id, company_id
|
||||
db, id, pedimento_id, tenant_id, company_id
|
||||
)
|
||||
if not success:
|
||||
raise HTTPException(status_code=404, detail="Payment not found")
|
||||
|
||||
@@ -6,7 +6,8 @@ import logging
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from sqlalchemy import desc
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.orm import Session, joinedload
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
from ..dtos.pedimentos import PedimentosCreate, PedimentosUpdate
|
||||
|
||||
@@ -85,8 +86,31 @@ class PedimentosService:
|
||||
query = query.filter(Pedimentos.year == filters["year"])
|
||||
|
||||
total = query.count()
|
||||
|
||||
# Eager load all relationships for the response schema
|
||||
items = (
|
||||
query.order_by(desc(Pedimentos.created_at)).offset(skip).limit(limit).all()
|
||||
query.options(
|
||||
selectinload(Pedimentos.pedimento_dates),
|
||||
selectinload(Pedimentos.pedimento_decrementables),
|
||||
selectinload(Pedimentos.pedimento_incrementables),
|
||||
selectinload(Pedimentos.pedimento_indexes),
|
||||
selectinload(Pedimentos.pedimento_validation),
|
||||
selectinload(Pedimentos.pedimento_customs_offices),
|
||||
selectinload(Pedimentos.pedimento_payments),
|
||||
selectinload(Pedimentos.pedimento_rectification_destination),
|
||||
selectinload(Pedimentos.pedimento_rectification_origin),
|
||||
selectinload(Pedimentos.pedimento_transport_means),
|
||||
selectinload(Pedimentos.pedimento_config_additional),
|
||||
selectinload(Pedimentos.pedimento_config_calculations),
|
||||
selectinload(Pedimentos.pedimento_config_parameters),
|
||||
selectinload(Pedimentos.pedimento_config_surcharges),
|
||||
selectinload(Pedimentos.pedimento_config_update_rectification),
|
||||
selectinload(Pedimentos.pedimento_config_updates),
|
||||
)
|
||||
.order_by(desc(Pedimentos.created_at))
|
||||
.offset(skip)
|
||||
.limit(limit)
|
||||
.all()
|
||||
)
|
||||
|
||||
return items, total
|
||||
@@ -113,6 +137,26 @@ class PedimentosService:
|
||||
|
||||
if company_id is not None:
|
||||
query = query.filter(Pedimentos.company_id == company_id)
|
||||
|
||||
# Eager load all relationships for the response schema
|
||||
query = query.options(
|
||||
selectinload(Pedimentos.pedimento_dates),
|
||||
selectinload(Pedimentos.pedimento_decrementables),
|
||||
selectinload(Pedimentos.pedimento_incrementables),
|
||||
selectinload(Pedimentos.pedimento_indexes),
|
||||
selectinload(Pedimentos.pedimento_validation),
|
||||
selectinload(Pedimentos.pedimento_customs_offices),
|
||||
selectinload(Pedimentos.pedimento_payments),
|
||||
selectinload(Pedimentos.pedimento_rectification_destination),
|
||||
selectinload(Pedimentos.pedimento_rectification_origin),
|
||||
selectinload(Pedimentos.pedimento_transport_means),
|
||||
selectinload(Pedimentos.pedimento_config_additional),
|
||||
selectinload(Pedimentos.pedimento_config_calculations),
|
||||
selectinload(Pedimentos.pedimento_config_parameters),
|
||||
selectinload(Pedimentos.pedimento_config_surcharges),
|
||||
selectinload(Pedimentos.pedimento_config_update_rectification),
|
||||
selectinload(Pedimentos.pedimento_config_updates),
|
||||
)
|
||||
|
||||
return query.first()
|
||||
|
||||
@@ -247,23 +291,25 @@ class PedimentosService:
|
||||
|
||||
# Helper function para actualizar o crear objetos relacionados
|
||||
def update_or_create_related(service_class, model_class, data_attr):
|
||||
if not hasattr(pedimento_data, data_attr):
|
||||
# Obtener datos del payload completo (no solo exclude_unset)
|
||||
full_data = pedimento_data.model_dump()
|
||||
|
||||
if data_attr not in full_data:
|
||||
return
|
||||
|
||||
data = getattr(pedimento_data, data_attr)
|
||||
data = full_data[data_attr]
|
||||
if not data:
|
||||
return
|
||||
|
||||
existing = service_class.get_by_pedimento_id(db, pedimento_id, tenant_id)
|
||||
if existing:
|
||||
# Actualizar existente
|
||||
update_dict = data.model_dump(exclude_unset=True)
|
||||
for field, value in update_dict.items():
|
||||
setattr(existing, field, value)
|
||||
for field, value in data.items():
|
||||
if hasattr(existing, field):
|
||||
setattr(existing, field, value)
|
||||
else:
|
||||
# Crear nuevo
|
||||
obj_dict = data.model_dump()
|
||||
obj = model_class(**obj_dict)
|
||||
obj = model_class(**data)
|
||||
obj.pedimento_id = pedimento_id
|
||||
obj.tenant_id = tenant_id
|
||||
obj.company_id = company_id
|
||||
|
||||
@@ -15,11 +15,22 @@ router = APIRouter(prefix="/code-pedimento-regimens")
|
||||
def list_code_pedimento_regimens(
|
||||
page: int = Query(1, ge=1, description="Número de página"),
|
||||
page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"),
|
||||
code: str = Query(None, description="Filter by code"),
|
||||
regime: str = Query(None, description="Filter by regime"),
|
||||
type: str = Query(None, description="Filter by type"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
skip = (page - 1) * page_size
|
||||
query = db.query(CodePedimentoRegimen)
|
||||
|
||||
if code is not None:
|
||||
query = query.filter(CodePedimentoRegimen.pedimento_code == code)
|
||||
if regime is not None:
|
||||
query = query.filter(CodePedimentoRegimen.regime == regime)
|
||||
if type is not None:
|
||||
query = query.filter(CodePedimentoRegimen.type == type)
|
||||
|
||||
items = query.offset(skip).limit(page_size).all()
|
||||
total = query.count()
|
||||
return {
|
||||
|
||||
@@ -31,8 +31,8 @@ export interface ClientProvider {
|
||||
domicile_fiscal?: string | null;
|
||||
foreign_tax_id?: string | null;
|
||||
client_or_provider?: string | null;
|
||||
enabled_disabled?: number;
|
||||
tenant_id: number;
|
||||
is_active?: boolean;
|
||||
tenant_id: number;
|
||||
address?: ClientProviderAddress | null;
|
||||
programs?: ClientProviderPrograms | null;
|
||||
}
|
||||
@@ -46,7 +46,7 @@ export interface ClientProviderBasic {
|
||||
domicile_fiscal?: string | null;
|
||||
foreign_tax_id?: string | null;
|
||||
client_or_provider?: string | null;
|
||||
enabled_disabled?: number;
|
||||
is_active?: boolean;
|
||||
tenant_id: number;
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ export interface CreateClientProviderData {
|
||||
domicile_fiscal?: string | null;
|
||||
foreign_tax_id?: string | null;
|
||||
client_or_provider?: string | null;
|
||||
enabled_disabled?: number;
|
||||
is_active?: boolean;
|
||||
address?: Omit<ClientProviderAddress, 'id' | 'client_id'> | null;
|
||||
programs?: Omit<ClientProviderPrograms, 'id' | 'client_id'> | null;
|
||||
}
|
||||
@@ -78,7 +78,7 @@ export interface UpdateClientProviderData {
|
||||
domicile_fiscal?: string | null;
|
||||
foreign_tax_id?: string | null;
|
||||
client_or_provider?: string | null;
|
||||
enabled_disabled?: number;
|
||||
is_active?: boolean;
|
||||
address?: Partial<ClientProviderAddress> | null;
|
||||
programs?: Partial<ClientProviderPrograms> | null;
|
||||
}
|
||||
@@ -100,7 +100,7 @@ export const clientsProvidersApi = {
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString()
|
||||
});
|
||||
|
||||
|
||||
if (filters) {
|
||||
Object.entries(filters).forEach(([key, value]) => {
|
||||
if (value !== undefined && value !== null && value !== '') {
|
||||
@@ -108,7 +108,7 @@ export const clientsProvidersApi = {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return api.get<ClientProviderListResponse>(
|
||||
`/v1/a76/clients-providers?${params.toString()}`
|
||||
);
|
||||
@@ -166,7 +166,7 @@ export const clientsProvidersApi = {
|
||||
|
||||
/**
|
||||
* Crea un nuevo cliente/proveedor
|
||||
* @param companyId - ID de la compañía
|
||||
* @param companyId - ID de la compañía
|
||||
* @param data - Datos del cliente/proveedor a crear
|
||||
*/
|
||||
create: (companyId: number, data: CreateClientProviderData) =>
|
||||
|
||||
@@ -19,7 +19,7 @@ export interface CustomsBroker {
|
||||
tax_id?: string | null;
|
||||
personal_id?: string | null;
|
||||
position?: string | null;
|
||||
license?: string | null;
|
||||
license: string;
|
||||
company?: string | null;
|
||||
contact?: string | null;
|
||||
tenant_id: string;
|
||||
@@ -117,6 +117,14 @@ export const customsBrokersApi = {
|
||||
return api.delete<CustomsBroker>(`/v1/a76/customs-brokers/${brokerKey}?company_id=${companyId}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* Actualiza la información de un agente aduanal
|
||||
*/
|
||||
update: (brokerKey: string, data: CreateCustomsBrokerData) => {
|
||||
const companyId = data.company_id;
|
||||
return api.put<CustomsBroker>(`/v1/a76/customs-brokers/${brokerKey}?company_id=${companyId}`, data);
|
||||
},
|
||||
|
||||
/**
|
||||
* Actualiza la información de VU de un agente aduanal
|
||||
*/
|
||||
|
||||
@@ -17,8 +17,6 @@ export interface PedimentoDates {
|
||||
original_date?: string | null;
|
||||
start_date?: string | null;
|
||||
end_date?: string | null;
|
||||
capture_date?: string | null;
|
||||
capture_time?: string | null;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
@@ -33,8 +31,6 @@ export interface CreatePedimentoDatesData {
|
||||
original_date?: string | null;
|
||||
start_date?: string | null;
|
||||
end_date?: string | null;
|
||||
capture_date?: string | null;
|
||||
capture_time?: string | null;
|
||||
}
|
||||
|
||||
export interface UpdatePedimentoDatesData {
|
||||
@@ -48,8 +44,6 @@ export interface UpdatePedimentoDatesData {
|
||||
original_date?: string | null;
|
||||
start_date?: string | null;
|
||||
end_date?: string | null;
|
||||
capture_date?: string | null;
|
||||
capture_time?: string | null;
|
||||
}
|
||||
|
||||
export const pedimentoDatesApi = {
|
||||
|
||||
@@ -17,8 +17,7 @@ export interface PedimentoPayments {
|
||||
total_cash_paid?: number | null;
|
||||
total_contributions?: number | null;
|
||||
counter_payment?: number | null;
|
||||
pece_code?: string | null;
|
||||
payment_id?: number | null;
|
||||
pece_code?: string | null;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
@@ -33,8 +32,7 @@ export interface CreatePedimentoPaymentsData {
|
||||
total_cash_paid?: number | null;
|
||||
total_contributions?: number | null;
|
||||
counter_payment?: number | null;
|
||||
pece_code?: string | null;
|
||||
payment_id?: number | null;
|
||||
pece_code?: string | null;
|
||||
}
|
||||
|
||||
export interface UpdatePedimentoPaymentsData {
|
||||
@@ -48,8 +46,7 @@ export interface UpdatePedimentoPaymentsData {
|
||||
total_cash_paid?: number | null;
|
||||
total_contributions?: number | null;
|
||||
counter_payment?: number | null;
|
||||
pece_code?: string | null;
|
||||
payment_id?: number | null;
|
||||
pece_code?: string | null;
|
||||
}
|
||||
|
||||
export const pedimentoPaymentsApi = {
|
||||
|
||||
@@ -9,22 +9,45 @@ export interface PedimentoDates {
|
||||
entry_date?: string | null;
|
||||
pedimento_date?: string | null;
|
||||
payment_date?: string | null;
|
||||
rectification_payment_date?: string | null;
|
||||
extraction_date?: string | null;
|
||||
submission_date?: string | null;
|
||||
eucan_date?: string | null;
|
||||
original_date?: string | null;
|
||||
start_date?: string | null;
|
||||
end_date?: string | null;
|
||||
}
|
||||
|
||||
export interface PedimentoPayments {
|
||||
payment_form?: string | null;
|
||||
bank_identifier?: string | null;
|
||||
acknowledgment?: string | null;
|
||||
operation_number?: string | null;
|
||||
bank_code?: string | null;
|
||||
cashier?: string | null;
|
||||
date?: string | null;
|
||||
time?: string | null;
|
||||
shift?: string | null;
|
||||
total_cash_paid?: string | null;
|
||||
total_contributions?: string | null;
|
||||
counter_payment?: string | null;
|
||||
pece_code?: string | null;
|
||||
}
|
||||
|
||||
export interface PedimentoTransportMeans {
|
||||
arrival_key?: string | null;
|
||||
arrival_data?: string | null;
|
||||
departure_key?: string | null;
|
||||
departure_data?: string | null;
|
||||
destination?: number | null;
|
||||
entry_exit?: string | null;
|
||||
arrival?: string | null;
|
||||
departure?: string | null;
|
||||
}
|
||||
|
||||
export interface PedimentoValidation {
|
||||
document?: string | null;
|
||||
validator?: string | null;
|
||||
validation_ack?: string | null;
|
||||
pre_ack?: string | null;
|
||||
line_signature?: string | null;
|
||||
electronic_signature?: string | null;
|
||||
certificate_number?: string | null;
|
||||
validator_id?: number | null;
|
||||
responsible_id?: number | null;
|
||||
}
|
||||
|
||||
export interface Pedimento {
|
||||
@@ -49,7 +72,7 @@ export interface Pedimento {
|
||||
pedimento_dates?: PedimentoDates | null;
|
||||
pedimento_payments?: PedimentoPayments | null;
|
||||
pedimento_transport_means?: PedimentoTransportMeans | null;
|
||||
pedimento_validation?: PedimentoValidation | null;
|
||||
pedimento_validation?: PedimentoValidation | null;
|
||||
}
|
||||
|
||||
export interface PedimentoListResponse {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import { materialTypesApi, type MaterialType } from "$lib/api/dashboard/refrence_data/material_types";
|
||||
import { companyStore } from "$lib/stores/company.svelte";
|
||||
import { onMount } from 'svelte';
|
||||
import { LoaderCircle, Home } from 'lucide-svelte';
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
@@ -248,21 +249,7 @@
|
||||
{#if companyStore.activeCompany}
|
||||
<div class="rounded-md bg-blue-50 border border-blue-200 p-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="text-blue-600"
|
||||
>
|
||||
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
|
||||
<polyline points="9 22 9 12 15 12 15 22" />
|
||||
</svg>
|
||||
<Home class="h-4 w-4 text-blue-600" />
|
||||
<div>
|
||||
<p class="text-sm font-medium text-blue-900">
|
||||
{companyStore.activeCompany.name}
|
||||
@@ -426,26 +413,7 @@
|
||||
</Button>
|
||||
<Button type="submit" disabled={loading}>
|
||||
{#if loading}
|
||||
<svg
|
||||
class="mr-2 h-4 w-4 animate-spin"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle
|
||||
class="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
stroke-width="4"
|
||||
></circle>
|
||||
<path
|
||||
class="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path>
|
||||
</svg>
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
Guardando...
|
||||
{:else}
|
||||
{isEdit ? 'Actualizar' : 'Crear'}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
|
||||
import { classesApi, type A76Class } from "$lib/api/dashboard/a76/classes";
|
||||
import { companyStore } from "$lib/stores/company.svelte";
|
||||
import { EllipsisVertical, Pencil, LoaderCircle, Trash2 } from 'lucide-svelte';
|
||||
import CreateEditDialog from "./create-edit-dialog.svelte";
|
||||
|
||||
let {
|
||||
@@ -80,21 +81,7 @@
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="relative size-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<circle cx="12" cy="12" r="1" />
|
||||
<circle cx="12" cy="5" r="1" />
|
||||
<circle cx="12" cy="19" r="1" />
|
||||
</svg>
|
||||
<EllipsisVertical size={16} />
|
||||
</Button>
|
||||
{/snippet}
|
||||
</DropdownMenu.Trigger>
|
||||
@@ -102,63 +89,15 @@
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleEdit}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z" />
|
||||
<path d="m15 5 4 4" />
|
||||
</svg>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleDelete} class="text-destructive" disabled={loading}>
|
||||
{#if loading}
|
||||
<svg
|
||||
class="mr-2 h-4 w-4 animate-spin"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle
|
||||
class="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
stroke-width="4"
|
||||
></circle>
|
||||
<path
|
||||
class="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path>
|
||||
</svg>
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M3 6h18" />
|
||||
<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" />
|
||||
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" />
|
||||
</svg>
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
|
||||
@@ -56,10 +56,10 @@ export function createColumns(onSuccess?: () => void): ColumnDef<ClientProvider>
|
||||
const typeSnippet = createRawSnippet<[{ type: string | null | undefined }]>((getType) => {
|
||||
const { type } = getType();
|
||||
const displayType = type === 'client' ? 'Cliente' : type === 'provider' ? 'Proveedor' : type === 'both' ? 'Ambos' : 'N/A';
|
||||
const colorClass = type === 'client' ? 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300'
|
||||
const colorClass = type === 'client' ? 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300'
|
||||
: type === 'provider' ? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300'
|
||||
: type === 'both' ? 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-300'
|
||||
: 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-300';
|
||||
: type === 'both' ? 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-300'
|
||||
: 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-300';
|
||||
return {
|
||||
render: () => `<span class="inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ${colorClass}">${displayType}</span>`
|
||||
};
|
||||
@@ -81,21 +81,21 @@ export function createColumns(onSuccess?: () => void): ColumnDef<ClientProvider>
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: "enabled_disabled",
|
||||
accessorKey: "is_active",
|
||||
header: "Estado",
|
||||
cell: ({ row }) => {
|
||||
const statusSnippet = createRawSnippet<[{ status: number | undefined }]>((getStatus) => {
|
||||
const { status } = getStatus();
|
||||
const isEnabled = status === 1;
|
||||
const statusText = isEnabled ? 'Activo' : 'Inactivo';
|
||||
const colorClass = isEnabled
|
||||
? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300'
|
||||
const colorClass = isEnabled
|
||||
? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300'
|
||||
: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300';
|
||||
return {
|
||||
render: () => `<span class="inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ${colorClass}">${statusText}</span>`
|
||||
};
|
||||
});
|
||||
return renderSnippet(statusSnippet, { status: row.original.enabled_disabled });
|
||||
return renderSnippet(statusSnippet, { status: row.original.is_active });
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import { clientsProvidersApi, type ClientProvider, type CreateClientProviderData, type UpdateClientProviderData } from "$lib/api/dashboard/a76/clients-providers";
|
||||
import { companyStore } from "$lib/stores/company.svelte";
|
||||
import { LoaderCircle } from 'lucide-svelte';
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
@@ -24,7 +25,7 @@
|
||||
domicile_fiscal: "",
|
||||
foreign_tax_id: "",
|
||||
client_or_provider: "client",
|
||||
enabled_disabled: 1,
|
||||
is_active: true,
|
||||
// Address fields
|
||||
street: "",
|
||||
neighborhood: "",
|
||||
@@ -51,7 +52,7 @@
|
||||
domicile_fiscal: item.domicile_fiscal || "",
|
||||
foreign_tax_id: item.foreign_tax_id || "",
|
||||
client_or_provider: item.client_or_provider || "client",
|
||||
enabled_disabled: item.enabled_disabled ?? 1,
|
||||
is_active: item.is_active ?? true,
|
||||
street: item.address?.street || "",
|
||||
neighborhood: item.address?.neighborhood || "",
|
||||
city: item.address?.city || "",
|
||||
@@ -70,7 +71,7 @@
|
||||
domicile_fiscal: "",
|
||||
foreign_tax_id: "",
|
||||
client_or_provider: "client",
|
||||
enabled_disabled: 1,
|
||||
is_active: true,
|
||||
street: "",
|
||||
neighborhood: "",
|
||||
city: "",
|
||||
@@ -107,7 +108,7 @@
|
||||
domicile_fiscal: formData.domicile_fiscal || null,
|
||||
foreign_tax_id: formData.foreign_tax_id || null,
|
||||
client_or_provider: formData.client_or_provider as "client" | "provider" | "both" | null,
|
||||
enabled_disabled: formData.enabled_disabled,
|
||||
is_active: formData.is_active,
|
||||
address: {
|
||||
street: formData.street || null,
|
||||
neighborhood: formData.neighborhood || null,
|
||||
@@ -132,7 +133,7 @@
|
||||
domicile_fiscal: formData.domicile_fiscal || null,
|
||||
foreign_tax_id: formData.foreign_tax_id || null,
|
||||
client_or_provider: formData.client_or_provider as "client" | "provider" | "both" | null,
|
||||
enabled_disabled: formData.enabled_disabled,
|
||||
is_active: formData.is_active,
|
||||
address: {
|
||||
street: formData.street || null,
|
||||
neighborhood: formData.neighborhood || null,
|
||||
@@ -185,7 +186,7 @@
|
||||
domicile_fiscal: "",
|
||||
foreign_tax_id: "",
|
||||
client_or_provider: "client",
|
||||
enabled_disabled: 1,
|
||||
is_active: true,
|
||||
street: "",
|
||||
neighborhood: "",
|
||||
city: "",
|
||||
@@ -432,26 +433,7 @@
|
||||
</Button>
|
||||
<Button type="submit" disabled={loading}>
|
||||
{#if loading}
|
||||
<svg
|
||||
class="mr-2 h-4 w-4 animate-spin"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle
|
||||
class="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
stroke-width="4"
|
||||
></circle>
|
||||
<path
|
||||
class="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path>
|
||||
</svg>
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{/if}
|
||||
{isEditing ? "Guardar cambios" : "Crear"}
|
||||
</Button>
|
||||
|
||||
@@ -90,7 +90,7 @@
|
||||
<DropdownMenu.Item onclick={handleViewDetails}>Ver detalles</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={handleEdit}>Editar</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={handleToggleStatus} disabled={isToggling}>
|
||||
{isToggling ? 'Cambiando...' : item.enabled_disabled === 1 ? 'Desactivar' : 'Activar'}
|
||||
{isToggling ? 'Cambiando...' : item.is_active === true ? 'Desactivar' : 'Activar'}
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item class="text-destructive" onclick={handleDelete}>Eliminar</DropdownMenu.Item>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import * as AlertDialog from "$lib/components/ui/alert-dialog";
|
||||
import { clientsProvidersApi, type ClientProvider } from "$lib/api/dashboard/a76/clients-providers";
|
||||
import { companyStore } from "$lib/stores/company.svelte";
|
||||
import { LoaderCircle } from 'lucide-svelte';
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
@@ -97,26 +98,7 @@
|
||||
class="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
>
|
||||
{#if loading}
|
||||
<svg
|
||||
class="mr-2 h-4 w-4 animate-spin"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle
|
||||
class="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
stroke-width="4"
|
||||
></circle>
|
||||
<path
|
||||
class="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path>
|
||||
</svg>
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</AlertDialog.Action>
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-xs font-medium text-muted-foreground">Estado</span>
|
||||
{#if item.enabled_disabled === 1}
|
||||
{#if item.is_active === true}
|
||||
<span class="inline-flex w-fit items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-800 dark:bg-green-900 dark:text-green-300">
|
||||
Activo
|
||||
</span>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import type { CustomsBroker } from "./columns.js";
|
||||
import DetailsDialog from "./details-dialog.svelte";
|
||||
import DeleteDialog from "./delete-dialog.svelte";
|
||||
import EditDialog from "./edit-dialog.svelte";
|
||||
|
||||
let {
|
||||
broker,
|
||||
@@ -16,6 +17,7 @@
|
||||
|
||||
let showDetailsDialog = $state(false);
|
||||
let showDeleteDialog = $state(false);
|
||||
let showEditDialog = $state(false);
|
||||
|
||||
function handleCopyKey() {
|
||||
navigator.clipboard.writeText(broker.broker_key);
|
||||
@@ -31,6 +33,10 @@
|
||||
showDetailsDialog = true;
|
||||
}
|
||||
|
||||
function handleEdit() {
|
||||
showEditDialog = true;
|
||||
}
|
||||
|
||||
function handleDelete() {
|
||||
showDeleteDialog = true;
|
||||
}
|
||||
@@ -64,6 +70,9 @@
|
||||
<DropdownMenu.Item onclick={handleViewDetails}>
|
||||
Ver detalles
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={handleEdit}>
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={handleDelete} class="text-destructive">
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
@@ -72,4 +81,5 @@
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<DetailsDialog bind:open={showDetailsDialog} {broker} />
|
||||
<EditDialog bind:open={showEditDialog} {broker} {onSuccess} />
|
||||
<DeleteDialog bind:open={showDeleteDialog} {broker} {onSuccess} />
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as AlertDialog from "$lib/components/ui/alert-dialog";
|
||||
import { customsBrokersApi, type CustomsBroker } from "$lib/api/dashboard/a76/customs-brokers";
|
||||
import { LoaderCircle } from 'lucide-svelte';
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
@@ -92,26 +93,7 @@
|
||||
class="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
>
|
||||
{#if loading}
|
||||
<svg
|
||||
class="mr-2 h-4 w-4 animate-spin"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle
|
||||
class="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
stroke-width="4"
|
||||
></circle>
|
||||
<path
|
||||
class="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path>
|
||||
</svg>
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
Eliminando...
|
||||
{:else}
|
||||
Eliminar
|
||||
|
||||
@@ -0,0 +1,384 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as Dialog from "$lib/components/ui/dialog";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import { customsBrokersApi, type CreateCustomsBrokerData, type CustomsBroker } from "$lib/api/dashboard/a76/customs-brokers";
|
||||
import { companyStore } from "$lib/stores/company.svelte";
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
broker,
|
||||
onSuccess
|
||||
}: {
|
||||
open: boolean;
|
||||
broker: CustomsBroker;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
let formData = $state({
|
||||
name: "",
|
||||
type: "",
|
||||
address: "",
|
||||
postal_code: "",
|
||||
city: "",
|
||||
state: "",
|
||||
phone: "",
|
||||
fax: "",
|
||||
email: "",
|
||||
country: "",
|
||||
tax_id: "",
|
||||
personal_id: "",
|
||||
position: "",
|
||||
license: "",
|
||||
company: "",
|
||||
contact: ""
|
||||
});
|
||||
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
// Inicializar formulario cuando cambia el broker
|
||||
$effect(() => {
|
||||
if (open && broker) {
|
||||
formData = {
|
||||
name: broker.name || "",
|
||||
type: broker.type || "",
|
||||
address: broker.address || "",
|
||||
postal_code: broker.postal_code || "",
|
||||
city: broker.city || "",
|
||||
state: broker.state || "",
|
||||
phone: broker.phone || "",
|
||||
fax: broker.fax || "",
|
||||
email: broker.email || "",
|
||||
country: broker.country || "",
|
||||
tax_id: broker.tax_id || "",
|
||||
personal_id: broker.personal_id || "",
|
||||
position: broker.position || "",
|
||||
license: broker.license || "",
|
||||
company: broker.company || "",
|
||||
contact: broker.contact || ""
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
async function handleSubmit(e: Event) {
|
||||
e.preventDefault();
|
||||
|
||||
if (!companyStore.activeCompany) {
|
||||
error = "No hay compañía seleccionada";
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const payload: CreateCustomsBrokerData = {
|
||||
broker_key: broker.broker_key, // La clave no se edita
|
||||
name: formData.name || null,
|
||||
type: formData.type || null,
|
||||
address: formData.address || null,
|
||||
postal_code: formData.postal_code || null,
|
||||
city: formData.city || null,
|
||||
state: formData.state || null,
|
||||
phone: formData.phone || null,
|
||||
fax: formData.fax || null,
|
||||
email: formData.email || null,
|
||||
country: formData.country || null,
|
||||
tax_id: formData.tax_id || null,
|
||||
personal_id: formData.personal_id || null,
|
||||
position: formData.position || null,
|
||||
license: formData.license || null,
|
||||
company: formData.company || null,
|
||||
contact: formData.contact || null,
|
||||
tenant_id: broker.tenant_id,
|
||||
company_id: companyStore.activeCompany.id.toString()
|
||||
};
|
||||
|
||||
const response = await customsBrokersApi.update(broker.broker_key, payload);
|
||||
|
||||
if (response.error) {
|
||||
if (response.status === 401) {
|
||||
error = 'Sesión expirada. Recargando página...';
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1500);
|
||||
} else {
|
||||
error = response.error;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Éxito
|
||||
open = false;
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : "Error al guardar";
|
||||
console.error("Error updating:", e);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleOpenChange(newOpen: boolean) {
|
||||
open = newOpen;
|
||||
if (!newOpen) {
|
||||
error = null;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open onOpenChange={handleOpenChange}>
|
||||
<Dialog.Content class="sm:max-w-[700px] max-h-[90vh] overflow-y-auto">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>Editar Agente Aduanal</Dialog.Title>
|
||||
<Dialog.Description>
|
||||
Modifica los datos del agente aduanal <span class="font-mono font-semibold">{broker?.broker_key}</span>.
|
||||
</Dialog.Description>
|
||||
</Dialog.Header>
|
||||
|
||||
<form onsubmit={handleSubmit} class="space-y-6">
|
||||
{#if error}
|
||||
<div class="rounded-lg border border-destructive bg-destructive/10 p-3 text-sm text-destructive">
|
||||
{error}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Información básica -->
|
||||
<div class="space-y-4">
|
||||
<h3 class="text-sm font-semibold">Información Básica</h3>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="edit-broker_key">Clave</Label>
|
||||
<Input
|
||||
id="edit-broker_key"
|
||||
value={broker?.broker_key}
|
||||
disabled
|
||||
class="bg-muted"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="edit-type">Tipo</Label>
|
||||
<Input
|
||||
id="edit-type"
|
||||
bind:value={formData.type}
|
||||
placeholder="Tipo de agente"
|
||||
maxlength={9}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="edit-name">Nombre</Label>
|
||||
<Input
|
||||
id="edit-name"
|
||||
bind:value={formData.name}
|
||||
placeholder="Nombre del agente aduanal"
|
||||
maxlength={80}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="edit-license">Patente</Label>
|
||||
<Input
|
||||
id="edit-license"
|
||||
bind:value={formData.license}
|
||||
placeholder="Número de patente"
|
||||
maxlength={4}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="edit-company">Empresa</Label>
|
||||
<Input
|
||||
id="edit-company"
|
||||
bind:value={formData.company}
|
||||
placeholder="Empresa del agente"
|
||||
maxlength={200}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Información de contacto -->
|
||||
<div class="space-y-4">
|
||||
<h3 class="text-sm font-semibold">Información de Contacto</h3>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="edit-phone">Teléfono</Label>
|
||||
<Input
|
||||
id="edit-phone"
|
||||
bind:value={formData.phone}
|
||||
placeholder="Número telefónico"
|
||||
maxlength={30}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="edit-fax">Fax</Label>
|
||||
<Input
|
||||
id="edit-fax"
|
||||
bind:value={formData.fax}
|
||||
placeholder="Número de fax"
|
||||
maxlength={30}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="edit-email">Email</Label>
|
||||
<Input
|
||||
id="edit-email"
|
||||
type="email"
|
||||
bind:value={formData.email}
|
||||
placeholder="correo@ejemplo.com"
|
||||
maxlength={100}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="edit-contact">Contacto</Label>
|
||||
<Input
|
||||
id="edit-contact"
|
||||
bind:value={formData.contact}
|
||||
placeholder="Nombre del contacto"
|
||||
maxlength={80}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dirección -->
|
||||
<div class="space-y-4">
|
||||
<h3 class="text-sm font-semibold">Dirección</h3>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="edit-address">Dirección</Label>
|
||||
<Input
|
||||
id="edit-address"
|
||||
bind:value={formData.address}
|
||||
placeholder="Calle y número"
|
||||
maxlength={1500}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="edit-postal_code">Código Postal</Label>
|
||||
<Input
|
||||
id="edit-postal_code"
|
||||
bind:value={formData.postal_code}
|
||||
placeholder="C.P."
|
||||
maxlength={15}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="edit-city">Ciudad</Label>
|
||||
<Input
|
||||
id="edit-city"
|
||||
bind:value={formData.city}
|
||||
placeholder="Ciudad"
|
||||
maxlength={30}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="edit-state">Estado</Label>
|
||||
<Input
|
||||
id="edit-state"
|
||||
bind:value={formData.state}
|
||||
placeholder="Estado"
|
||||
maxlength={30}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="edit-country">País</Label>
|
||||
<Input
|
||||
id="edit-country"
|
||||
bind:value={formData.country}
|
||||
placeholder="País"
|
||||
maxlength={3}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Información fiscal -->
|
||||
<div class="space-y-4">
|
||||
<h3 class="text-sm font-semibold">Información Fiscal</h3>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="edit-tax_id">RFC</Label>
|
||||
<Input
|
||||
id="edit-tax_id"
|
||||
bind:value={formData.tax_id}
|
||||
placeholder="RFC"
|
||||
maxlength={30}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="edit-personal_id">CURP</Label>
|
||||
<Input
|
||||
id="edit-personal_id"
|
||||
bind:value={formData.personal_id}
|
||||
placeholder="CURP"
|
||||
maxlength={20}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="edit-position">Posición</Label>
|
||||
<Input
|
||||
id="edit-position"
|
||||
bind:value={formData.position}
|
||||
placeholder="Cargo o posición"
|
||||
maxlength={30}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Dialog.Footer>
|
||||
<Button type="button" variant="outline" onclick={() => (open = false)} disabled={loading}>
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button type="submit" disabled={loading}>
|
||||
{#if loading}
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="h-4 w-4 animate-spin rounded-full border-2 border-primary-foreground border-t-transparent"></div>
|
||||
Guardando...
|
||||
</div>
|
||||
{:else}
|
||||
Guardar Cambios
|
||||
{/if}
|
||||
</Button>
|
||||
</Dialog.Footer>
|
||||
</form>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
@@ -2,6 +2,7 @@
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
|
||||
import { pedimentosApi, type Pedimento } from "$lib/api/dashboard/a76/pedimentos";
|
||||
import { EllipsisVertical, Pencil, LoaderCircle, Trash2 } from 'lucide-svelte';
|
||||
|
||||
let {
|
||||
item,
|
||||
@@ -62,21 +63,7 @@
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="relative size-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<circle cx="12" cy="12" r="1" />
|
||||
<circle cx="12" cy="5" r="1" />
|
||||
<circle cx="12" cy="19" r="1" />
|
||||
</svg>
|
||||
<EllipsisVertical class="h-4 w-4" />
|
||||
</Button>
|
||||
{/snippet}
|
||||
</DropdownMenu.Trigger>
|
||||
@@ -84,63 +71,15 @@
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleEdit}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z" />
|
||||
<path d="m15 5 4 4" />
|
||||
</svg>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleDelete} class="text-destructive" disabled={loading}>
|
||||
{#if loading}
|
||||
<svg
|
||||
class="mr-2 h-4 w-4 animate-spin"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle
|
||||
class="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
stroke-width="4"
|
||||
></circle>
|
||||
<path
|
||||
class="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path>
|
||||
</svg>
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M3 6h18" />
|
||||
<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" />
|
||||
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" />
|
||||
</svg>
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
|
||||
@@ -1,90 +1,40 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import { Skeleton } from '$lib/components/ui/skeleton';
|
||||
import { pedimentoDatesApi, type PedimentoDates } from '$lib/api/dashboard/a76/pedimento-dates';
|
||||
import type { Pedimento } from '$lib/api/dashboard/a76/pedimentos';
|
||||
|
||||
let {
|
||||
pedimentoId,
|
||||
pedimento,
|
||||
formData = $bindable(),
|
||||
exists = $bindable()
|
||||
}: {
|
||||
pedimentoId: number | null;
|
||||
pedimento: Pedimento | null;
|
||||
formData?: any;
|
||||
exists?: boolean;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(true);
|
||||
|
||||
onMount(async () => {
|
||||
await loadDates();
|
||||
});
|
||||
|
||||
async function loadDates() {
|
||||
// Si no hay pedimentoId (modo creación), inicializar vacío
|
||||
if (!pedimentoId) {
|
||||
// Inicializar formData inmediatamente
|
||||
const datesData = pedimento?.pedimento_dates;
|
||||
if (datesData) {
|
||||
exists = true;
|
||||
if (!formData) {
|
||||
formData = {
|
||||
entry_date: '',
|
||||
pedimento_date: '',
|
||||
payment_date: '',
|
||||
rectification_payment_date: '',
|
||||
extraction_date: '',
|
||||
submission_date: '',
|
||||
eucan_date: '',
|
||||
original_date: '',
|
||||
start_date: '',
|
||||
end_date: '',
|
||||
capture_date: '',
|
||||
capture_time: ''
|
||||
entry_date: datesData.entry_date ? datesData.entry_date.substring(0, 10) : '',
|
||||
pedimento_date: datesData.pedimento_date ? datesData.pedimento_date.substring(0, 10) : '',
|
||||
payment_date: datesData.payment_date ? datesData.payment_date.substring(0, 10) : '',
|
||||
rectification_payment_date: datesData.rectification_payment_date ? datesData.rectification_payment_date.substring(0, 10) : '',
|
||||
extraction_date: datesData.extraction_date ? datesData.extraction_date.substring(0, 10) : '',
|
||||
submission_date: datesData.submission_date ? datesData.submission_date.substring(0, 10) : '',
|
||||
eucan_date: datesData.eucan_date ? datesData.eucan_date.substring(0, 10) : '',
|
||||
original_date: datesData.original_date ? datesData.original_date.substring(0, 10) : '',
|
||||
start_date: datesData.start_date ? datesData.start_date.substring(0, 10) : '',
|
||||
end_date: datesData.end_date ? datesData.end_date.substring(0, 10) : '',
|
||||
};
|
||||
exists = false;
|
||||
loading = false;
|
||||
return;
|
||||
}
|
||||
loading = true;
|
||||
|
||||
try {
|
||||
const response = await pedimentoDatesApi.get(pedimentoId);
|
||||
|
||||
if (response.error) {
|
||||
// No existe o hay error - inicializar vacío
|
||||
exists = false;
|
||||
formData = {
|
||||
entry_date: '',
|
||||
pedimento_date: '',
|
||||
payment_date: '',
|
||||
rectification_payment_date: '',
|
||||
extraction_date: '',
|
||||
submission_date: '',
|
||||
eucan_date: '',
|
||||
original_date: '',
|
||||
start_date: '',
|
||||
end_date: '',
|
||||
capture_date: '',
|
||||
capture_time: ''
|
||||
};
|
||||
} else if (response.data) {
|
||||
exists = true;
|
||||
formData = {
|
||||
entry_date: response.data.entry_date ? response.data.entry_date.substring(0, 10) : '',
|
||||
pedimento_date: response.data.pedimento_date ? response.data.pedimento_date.substring(0, 10) : '',
|
||||
payment_date: response.data.payment_date ? response.data.payment_date.substring(0, 10) : '',
|
||||
rectification_payment_date: response.data.rectification_payment_date ? response.data.rectification_payment_date.substring(0, 10) : '',
|
||||
extraction_date: response.data.extraction_date ? response.data.extraction_date.substring(0, 10) : '',
|
||||
submission_date: response.data.submission_date ? response.data.submission_date.substring(0, 10) : '',
|
||||
eucan_date: response.data.eucan_date ? response.data.eucan_date.substring(0, 10) : '',
|
||||
original_date: response.data.original_date ? response.data.original_date.substring(0, 10) : '',
|
||||
start_date: response.data.start_date ? response.data.start_date.substring(0, 10) : '',
|
||||
end_date: response.data.end_date ? response.data.end_date.substring(0, 10) : '',
|
||||
capture_date: response.data.capture_date ? response.data.capture_date.substring(0, 10) : '',
|
||||
capture_time: response.data.capture_time || ''
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error loading dates:', e);
|
||||
exists = false;
|
||||
} else {
|
||||
exists = false;
|
||||
if (!formData) {
|
||||
formData = {
|
||||
entry_date: '',
|
||||
pedimento_date: '',
|
||||
@@ -96,11 +46,8 @@
|
||||
original_date: '',
|
||||
start_date: '',
|
||||
end_date: '',
|
||||
capture_date: '',
|
||||
capture_time: ''
|
||||
};
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -113,15 +60,8 @@
|
||||
</Card.Description>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
{#if loading}
|
||||
<div class="space-y-4">
|
||||
<Skeleton class="h-10 w-full" />
|
||||
<Skeleton class="h-10 w-full" />
|
||||
<Skeleton class="h-10 w-full" />
|
||||
</div>
|
||||
{:else}
|
||||
<div class="space-y-6">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<div class="space-y-6">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<!-- Fecha de Entrada -->
|
||||
<div class="space-y-2">
|
||||
<Label for="entry_date">Fecha de Entrada</Label>
|
||||
@@ -220,29 +160,8 @@
|
||||
type="date"
|
||||
bind:value={formData.end_date}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Fecha de Captura -->
|
||||
<div class="space-y-2">
|
||||
<Label for="capture_date">Fecha de Captura</Label>
|
||||
<Input
|
||||
id="capture_date"
|
||||
type="date"
|
||||
bind:value={formData.capture_date}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Hora de Captura -->
|
||||
<div class="space-y-2">
|
||||
<Label for="capture_time">Hora de Captura</Label>
|
||||
<Input
|
||||
id="capture_time"
|
||||
type="time"
|
||||
bind:value={formData.capture_time}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
@@ -2,20 +2,178 @@
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import * as Select from '$lib/components/ui/select';
|
||||
import type { Pedimento } from '$lib/api/dashboard/a76/pedimentos';
|
||||
import type { PedimentoCode } from '$lib/api/dashboard/refrence_data/pedimento_codes';
|
||||
import type { CustomsSection } from '$lib/api/dashboard/refrence_data/customs_sections';
|
||||
import type { CustomsBroker } from '$lib/api/dashboard/a76/customs-brokers';
|
||||
import type { ClientProvider } from '$lib/api/dashboard/a76/clients-providers';
|
||||
import type { CodePedimentoRegimen } from '$lib/api/dashboard/refrence_data/code_pedimento_regimens';
|
||||
|
||||
let {
|
||||
pedimento,
|
||||
formData = $bindable()
|
||||
formData = $bindable(),
|
||||
pedimentoCodes = [],
|
||||
customsSections = [],
|
||||
customsBrokers = [],
|
||||
clients = [],
|
||||
codePedimentoRegimens = []
|
||||
}: {
|
||||
pedimento: Pedimento | null;
|
||||
formData?: any;
|
||||
pedimentoCodes?: PedimentoCode[];
|
||||
customsSections?: CustomsSection[];
|
||||
customsBrokers?: CustomsBroker[];
|
||||
clients?: ClientProvider[];
|
||||
codePedimentoRegimens?: CodePedimentoRegimen[];
|
||||
} = $props();
|
||||
|
||||
// Extraer regímenes únicos de codePedimentoRegimens
|
||||
const uniqueRegimens = $derived(
|
||||
Array.from(new Set(codePedimentoRegimens.map(r => r.regimen_code).filter((code): code is string => code !== null)))
|
||||
.sort()
|
||||
.map(code => ({ code, label: code }))
|
||||
);
|
||||
|
||||
// Funciones de mapeo entre type_code (E/I) y operation_type (1/2)
|
||||
function typeCodeToOperationType(typeCode: string | null | undefined): number | null {
|
||||
if (!typeCode) return null;
|
||||
// E = Exportación = 1, I = Importación = 2
|
||||
if (typeCode.toUpperCase() === 'E') return 1;
|
||||
if (typeCode.toUpperCase() === 'I') return 2;
|
||||
return null;
|
||||
}
|
||||
|
||||
function operationTypeToTypeCode(operationType: number | null | undefined): string | null {
|
||||
if (operationType === null || operationType === undefined) return null;
|
||||
// 1 = Exportación = E, 2 = Importación = I
|
||||
if (operationType === 1) return 'E';
|
||||
if (operationType === 2) return 'I';
|
||||
return null;
|
||||
}
|
||||
|
||||
// Opciones filtradas para Régimen y Tipo de Operación basadas en las selecciones actuales
|
||||
// NOTA: La Clave NO se filtra, siempre muestra todas las opciones
|
||||
|
||||
const filteredRegimens = $derived(() => {
|
||||
// Si hay clave o tipo de operación seleccionado, filtrar
|
||||
if (formData.pedimento_code || formData.operation_type !== null) {
|
||||
const matches = codePedimentoRegimens.filter(r => {
|
||||
const matchesCode = !formData.pedimento_code || r.pedimento_code === formData.pedimento_code;
|
||||
const matchesType = formData.operation_type === null || r.type_code === operationTypeToTypeCode(formData.operation_type);
|
||||
return matchesCode && matchesType;
|
||||
});
|
||||
const validRegimens = new Set(matches.map(m => m.regimen_code).filter((code): code is string => code !== null));
|
||||
return Array.from(validRegimens).sort().map(code => ({ code, label: code }));
|
||||
}
|
||||
return uniqueRegimens;
|
||||
});
|
||||
|
||||
const filteredOperationTypes = $derived(() => {
|
||||
// Si hay clave o régimen seleccionado, filtrar
|
||||
if (formData.pedimento_code || formData.regime) {
|
||||
const matches = codePedimentoRegimens.filter(r => {
|
||||
const matchesCode = !formData.pedimento_code || r.pedimento_code === formData.pedimento_code;
|
||||
const matchesRegime = !formData.regime || r.regimen_code === formData.regime;
|
||||
return matchesCode && matchesRegime;
|
||||
});
|
||||
const validTypes = new Set(matches.map(m => typeCodeToOperationType(m.type_code)).filter(t => t !== null));
|
||||
return operationOptions.filter(opt => validTypes.has(opt.value));
|
||||
}
|
||||
return operationOptions;
|
||||
});
|
||||
|
||||
// Reactive synchronization between Clave, Régimen, and Tipo de Operación
|
||||
// REGLA: La Clave es el campo principal y NUNCA se modifica automáticamente
|
||||
// Solo se auto-llenan Régimen y Tipo de Operación basándose en la Clave
|
||||
|
||||
// Cuando cambia la Clave del Pedimento
|
||||
$effect(() => {
|
||||
const currentCode = formData.pedimento_code;
|
||||
if (!currentCode) return;
|
||||
|
||||
const matches = codePedimentoRegimens.filter(r => r.pedimento_code === currentCode);
|
||||
|
||||
if (matches.length === 0) return;
|
||||
|
||||
// Verificar si los valores actuales de régimen y tipo son válidos para esta clave
|
||||
const currentIsValid = matches.some(m => {
|
||||
const matchesRegime = !formData.regime || m.regimen_code === formData.regime;
|
||||
const matchesType = formData.operation_type === null || m.type_code === operationTypeToTypeCode(formData.operation_type);
|
||||
return matchesRegime && matchesType;
|
||||
});
|
||||
|
||||
// Si los valores actuales son válidos, NO auto-llenar
|
||||
if (currentIsValid && (formData.regime || formData.operation_type !== null)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Si solo hay un match y no hay valores válidos, auto-llenar
|
||||
if (matches.length === 1) {
|
||||
const match = matches[0];
|
||||
if (match.regimen_code && formData.regime !== match.regimen_code) {
|
||||
formData.regime = match.regimen_code;
|
||||
}
|
||||
const expectedOpType = typeCodeToOperationType(match.type_code);
|
||||
if (expectedOpType !== null && formData.operation_type !== expectedOpType) {
|
||||
formData.operation_type = expectedOpType;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Cuando cambia el Régimen
|
||||
$effect(() => {
|
||||
const currentRegime = formData.regime;
|
||||
if (!currentRegime) return;
|
||||
|
||||
const matches = codePedimentoRegimens.filter(r => r.regimen_code === currentRegime);
|
||||
|
||||
if (matches.length === 0) return;
|
||||
|
||||
// Si hay clave seleccionada, solo validar (NO auto-llenar tipo de operación)
|
||||
if (formData.pedimento_code) {
|
||||
const exactMatch = matches.find(m => m.pedimento_code === formData.pedimento_code);
|
||||
}
|
||||
// Si hay tipo de operación pero no clave, no hacer nada
|
||||
// (el usuario debe seleccionar la clave primero)
|
||||
});
|
||||
|
||||
// Cuando cambia el Tipo de Operación
|
||||
$effect(() => {
|
||||
const currentType = formData.operation_type;
|
||||
if (currentType === null || currentType === undefined) return;
|
||||
|
||||
const expectedTypeCode = operationTypeToTypeCode(currentType);
|
||||
const matches = codePedimentoRegimens.filter(r => r.type_code === expectedTypeCode);
|
||||
|
||||
if (matches.length === 0) return;
|
||||
|
||||
// Si hay clave seleccionada, actualizar régimen (forzar si no hay match exacto)
|
||||
if (formData.pedimento_code) {
|
||||
const exactMatch = matches.find(m => m.pedimento_code === formData.pedimento_code);
|
||||
if (exactMatch?.regimen_code && formData.regime !== exactMatch.regimen_code) {
|
||||
formData.regime = exactMatch.regimen_code;
|
||||
} else if (!exactMatch) {
|
||||
// No hay match exacto - buscar cualquier match con la clave actual
|
||||
const allMatchesForClave = codePedimentoRegimens.filter(r => r.pedimento_code === formData.pedimento_code);
|
||||
if (allMatchesForClave.length > 0) {
|
||||
// Forzar el régimen al primer match disponible para esta clave
|
||||
const firstMatch = allMatchesForClave[0];
|
||||
if (firstMatch.regimen_code) formData.regime = firstMatch.regimen_code;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Si hay régimen pero no clave, no hacer nada
|
||||
// (el usuario debe seleccionar la clave primero)
|
||||
});
|
||||
|
||||
// Obtener el año actual (últimos 2 dígitos)
|
||||
const currentYear = String(new Date().getFullYear()).slice(-2);
|
||||
|
||||
// Inicializar formData con los valores del pedimento (o vacío si es null)
|
||||
if (!formData) {
|
||||
formData = {
|
||||
year: pedimento?.year || '',
|
||||
year: pedimento?.year || currentYear,
|
||||
customs_office: pedimento?.customs_office || '',
|
||||
license: pedimento?.license || '',
|
||||
pedimento_number: pedimento?.pedimento_number || '',
|
||||
@@ -32,6 +190,18 @@
|
||||
};
|
||||
}
|
||||
|
||||
// Asegurar que el año siempre esté actualizado con el año actual
|
||||
$effect(() => {
|
||||
if (formData && !pedimento?.year) {
|
||||
formData.year = currentYear;
|
||||
}
|
||||
});
|
||||
|
||||
const operationOptions = [
|
||||
{ value: 1, label: 'Exportación' },
|
||||
{ value: 2, label: 'Importación' },
|
||||
];
|
||||
|
||||
const statusOptions = [
|
||||
{ value: 'MODIFICABLE', label: 'Modificable' },
|
||||
{ value: 'ESPERA FIRMA PREVIO', label: 'Espera de firma previo' },
|
||||
@@ -71,6 +241,8 @@
|
||||
placeholder="23"
|
||||
maxlength={2}
|
||||
class="text-center"
|
||||
disabled
|
||||
readonly
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -78,15 +250,28 @@
|
||||
<div class="pb-2 text-2xl font-semibold text-muted-foreground">-</div>
|
||||
|
||||
<!-- Aduana -->
|
||||
<div class="space-y-2 w-16">
|
||||
<div class="space-y-2 w-20">
|
||||
<Label for="customs_office">Aduana</Label>
|
||||
<Input
|
||||
id="customs_office"
|
||||
bind:value={formData.customs_office}
|
||||
placeholder="01"
|
||||
maxlength={2}
|
||||
class="text-center"
|
||||
/>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.customs_office || ''}
|
||||
onValueChange={(v: string) => formData.customs_office = v ?? ''}
|
||||
>
|
||||
<Select.Trigger class="w-full">
|
||||
<span class="truncate">
|
||||
{formData.customs_office || 'Sel...'}
|
||||
</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-w-[300px] max-h-[300px]">
|
||||
{#each customsSections as section}
|
||||
<Select.Item value={section.customs_code}>
|
||||
<span class="truncate overflow-hidden text-ellipsis whitespace-nowrap" title={`${section.customs_code} - ${section.section_name}`}>
|
||||
{section.customs_code} - {section.section_name}
|
||||
</span>
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
|
||||
<!-- Separador -->
|
||||
@@ -95,13 +280,26 @@
|
||||
<!-- Patente -->
|
||||
<div class="space-y-2 w-24">
|
||||
<Label for="license">Patente</Label>
|
||||
<Input
|
||||
id="license"
|
||||
bind:value={formData.license}
|
||||
placeholder="1234"
|
||||
maxlength={4}
|
||||
class="text-center"
|
||||
/>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.license || ''}
|
||||
onValueChange={(v: string) => formData.license = v ?? ''}
|
||||
>
|
||||
<Select.Trigger class="w-full">
|
||||
<span class="truncate">
|
||||
{formData.license || 'Sel...'}
|
||||
</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-w-[300px] max-h-[300px]">
|
||||
{#each customsBrokers as broker}
|
||||
<Select.Item value={broker.license}>
|
||||
<span class="truncate overflow-hidden text-ellipsis whitespace-nowrap" title={`${broker.broker_key} - ${broker.name || ''}`}>
|
||||
{broker.broker_key} - {broker.name || ''}
|
||||
</span>
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
|
||||
<!-- Separador -->
|
||||
@@ -123,24 +321,27 @@
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<!-- ID del Cliente -->
|
||||
<div class="space-y-2">
|
||||
<Label for="client_id">ID del Cliente</Label>
|
||||
<Input
|
||||
id="client_id"
|
||||
type="number"
|
||||
bind:value={formData.client_id}
|
||||
placeholder="Ej: 123"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Tipo de Operación -->
|
||||
<div class="space-y-2">
|
||||
<Label for="operation_type">Tipo de Operación</Label>
|
||||
<Input
|
||||
id="operation_type"
|
||||
type="number"
|
||||
bind:value={formData.operation_type}
|
||||
placeholder="Ej: 1"
|
||||
/>
|
||||
<Label for="client_id">Cliente</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={String(formData.client_id ?? '')}
|
||||
onValueChange={(v: string) => formData.client_id = v ? Number(v) : null}
|
||||
>
|
||||
<Select.Trigger class="w-full">
|
||||
<span class="truncate">
|
||||
{clients.find(c => c.id === formData.client_id)?.name || 'Seleccionar cliente...'}
|
||||
</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-h-[300px]">
|
||||
{#each clients as client}
|
||||
<Select.Item value={String(client.id)} label={client.name}>
|
||||
<span class="truncate overflow-hidden text-ellipsis whitespace-nowrap" title={client.name}>
|
||||
{client.name}
|
||||
</span>
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
|
||||
<!-- Tipo de Pedimento -->
|
||||
@@ -154,39 +355,99 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Clave del Pedimento -->
|
||||
<div class="space-y-2">
|
||||
<Label for="pedimento_code">Clave del Pedimento</Label>
|
||||
<Input
|
||||
id="pedimento_code"
|
||||
bind:value={formData.pedimento_code}
|
||||
placeholder="Ej: A1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Régimen -->
|
||||
<div class="space-y-2">
|
||||
<Label for="regime">Régimen</Label>
|
||||
<Input
|
||||
id="regime"
|
||||
bind:value={formData.regime}
|
||||
placeholder="Ej: IMD"
|
||||
/>
|
||||
</div>
|
||||
<!-- Fila: Clave (2), Régimen (3), Tipo de Operación (11) -->
|
||||
<div class="flex items-end gap-2">
|
||||
<!-- Clave del Pedimento -->
|
||||
<div class="space-y-2 w-20">
|
||||
<Label for="pedimento_code">Clave</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.pedimento_code || ''}
|
||||
onValueChange={(v: string) => formData.pedimento_code = v ?? ''}
|
||||
>
|
||||
<Select.Trigger class="w-full">
|
||||
<span class="truncate">
|
||||
{pedimentoCodes.find(o => o.code === formData.pedimento_code)?.code || 'Sel...'}
|
||||
</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-w-[200px]">
|
||||
{#each pedimentoCodes as code}
|
||||
<Select.Item value={code.code}>
|
||||
<span class="truncate overflow-hidden text-ellipsis whitespace-nowrap" title={`${code.code} - ${code.description}`}>
|
||||
{code.code} - {code.description}
|
||||
</span>
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
|
||||
<!-- Régimen -->
|
||||
<div class="space-y-2 w-24">
|
||||
<Label for="regime">Régimen</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.regime || ''}
|
||||
onValueChange={(v: string) => formData.regime = v ?? ''}
|
||||
>
|
||||
<Select.Trigger class="w-full">
|
||||
<span class="truncate">
|
||||
{formData.regime || 'Sel...'}
|
||||
</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-w-[200px]">
|
||||
{#each filteredRegimens() as regimen}
|
||||
<Select.Item value={regimen.code}>
|
||||
<span class="truncate overflow-hidden text-ellipsis whitespace-nowrap" title={regimen.code}>
|
||||
{regimen.code}
|
||||
</span>
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
|
||||
<!-- Tipo de Operación -->
|
||||
<div class="space-y-2 flex-1">
|
||||
<Label for="operation_type">Tipo de Operación</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={String(formData.operation_type ?? '')}
|
||||
onValueChange={(v: string) => formData.operation_type = v ? Number(v) : null}
|
||||
>
|
||||
<Select.Trigger class="w-full">
|
||||
{operationOptions.find(o => o.value === formData.operation_type)?.label || 'Seleccionar...'}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
{#each filteredOperationTypes() as option}
|
||||
<Select.Item value={String(option.value)} label={option.label} />
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
|
||||
<!-- Estado -->
|
||||
<div class="space-y-2">
|
||||
<Label for="status">Estado</Label>
|
||||
<select
|
||||
id="status"
|
||||
bind:value={formData.status}
|
||||
class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.status || ''}
|
||||
onValueChange={(v: string) => formData.status = v ?? ''}
|
||||
>
|
||||
<option value="">Seleccionar...</option>
|
||||
{#each statusOptions as option}
|
||||
<option value={option.value}>{option.label}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<Select.Trigger class="w-full">
|
||||
{statusOptions.find(o => o.value === formData.status)?.label || 'Seleccionar...'}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
{#each statusOptions as option}
|
||||
<Select.Item value={option.value} label={option.label} />
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
|
||||
<!-- Valor USD -->
|
||||
|
||||
@@ -1,106 +1,54 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import { Skeleton } from '$lib/components/ui/skeleton';
|
||||
import { pedimentoPaymentsApi } from '$lib/api/dashboard/a76/pedimento-payments';
|
||||
import type { Pedimento } from '$lib/api/dashboard/a76/pedimentos';
|
||||
|
||||
let {
|
||||
pedimentoId,
|
||||
pedimento,
|
||||
formData = $bindable(),
|
||||
exists = $bindable()
|
||||
}: {
|
||||
pedimentoId: number | null;
|
||||
pedimento: Pedimento | null;
|
||||
formData?: any;
|
||||
exists?: boolean;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(true);
|
||||
|
||||
onMount(async () => {
|
||||
await loadPayments();
|
||||
});
|
||||
|
||||
async function loadPayments() {
|
||||
// Si no hay pedimentoId (modo creación), inicializar vacío
|
||||
if (!pedimentoId) {
|
||||
// Inicializar formData inmediatamente
|
||||
const paymentsData = pedimento?.pedimento_payments;
|
||||
if (paymentsData) {
|
||||
exists = true;
|
||||
if (!formData) {
|
||||
formData = {
|
||||
acknowledgment: '',
|
||||
operation_number: '',
|
||||
bank_code: null,
|
||||
cashier: '',
|
||||
date: '',
|
||||
time: '',
|
||||
shift: '',
|
||||
total_cash_paid: null,
|
||||
total_contributions: null,
|
||||
counter_payment: null,
|
||||
pece_code: '',
|
||||
payment_id: null
|
||||
acknowledgment: paymentsData.acknowledgment || '',
|
||||
operation_number: paymentsData.operation_number || '',
|
||||
bank_code: paymentsData.bank_code || '',
|
||||
cashier: paymentsData.cashier || '',
|
||||
date: paymentsData.date ? paymentsData.date.substring(0, 10) : '',
|
||||
time: paymentsData.time || '',
|
||||
shift: paymentsData.shift || '',
|
||||
total_cash_paid: paymentsData.total_cash_paid || '',
|
||||
total_contributions: paymentsData.total_contributions || '',
|
||||
counter_payment: paymentsData.counter_payment || '',
|
||||
pece_code: paymentsData.pece_code || '',
|
||||
};
|
||||
exists = false;
|
||||
loading = false;
|
||||
return;
|
||||
}
|
||||
loading = true;
|
||||
|
||||
try {
|
||||
const response = await pedimentoPaymentsApi.get(pedimentoId);
|
||||
|
||||
if (response.error) {
|
||||
// No existe o hay error - inicializar vacío
|
||||
exists = false;
|
||||
formData = {
|
||||
acknowledgment: '',
|
||||
operation_number: '',
|
||||
bank_code: null,
|
||||
cashier: '',
|
||||
date: '',
|
||||
time: '',
|
||||
shift: '',
|
||||
total_cash_paid: null,
|
||||
total_contributions: null,
|
||||
counter_payment: null,
|
||||
pece_code: '',
|
||||
payment_id: null
|
||||
};
|
||||
} else if (response.data) {
|
||||
exists = true;
|
||||
formData = {
|
||||
acknowledgment: response.data.acknowledgment || '',
|
||||
operation_number: response.data.operation_number || '',
|
||||
bank_code: response.data.bank_code || null,
|
||||
cashier: response.data.cashier || '',
|
||||
date: response.data.date || '',
|
||||
time: response.data.time || '',
|
||||
shift: response.data.shift || '',
|
||||
total_cash_paid: response.data.total_cash_paid || null,
|
||||
total_contributions: response.data.total_contributions || null,
|
||||
counter_payment: response.data.counter_payment || null,
|
||||
pece_code: response.data.pece_code || '',
|
||||
payment_id: response.data.payment_id || null
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error loading payments:', e);
|
||||
exists = false;
|
||||
} else {
|
||||
exists = false;
|
||||
if (!formData) {
|
||||
formData = {
|
||||
acknowledgment: '',
|
||||
operation_number: '',
|
||||
bank_code: null,
|
||||
bank_code: '',
|
||||
cashier: '',
|
||||
date: '',
|
||||
time: '',
|
||||
shift: '',
|
||||
total_cash_paid: null,
|
||||
total_contributions: null,
|
||||
counter_payment: null,
|
||||
pece_code: '',
|
||||
payment_id: null
|
||||
total_cash_paid: '',
|
||||
total_contributions: '',
|
||||
counter_payment: '',
|
||||
pece_code: '',
|
||||
};
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -113,145 +61,128 @@
|
||||
</Card.Description>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
{#if loading}
|
||||
<div class="space-y-4">
|
||||
<Skeleton class="h-10 w-full" />
|
||||
<Skeleton class="h-10 w-full" />
|
||||
</div>
|
||||
{:else}
|
||||
<div class="space-y-6">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<!-- Acuse -->
|
||||
<div class="space-y-2">
|
||||
<Label for="acknowledgment">Acuse</Label>
|
||||
<Input
|
||||
id="acknowledgment"
|
||||
bind:value={formData.acknowledgment}
|
||||
placeholder="Máx. 20 caracteres"
|
||||
maxlength={20}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Número de Operación -->
|
||||
<div class="space-y-2">
|
||||
<Label for="operation_number">Número de Operación</Label>
|
||||
<Input
|
||||
id="operation_number"
|
||||
bind:value={formData.operation_number}
|
||||
placeholder="Máx. 14 caracteres"
|
||||
maxlength={14}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Código de Banco -->
|
||||
<div class="space-y-2">
|
||||
<Label for="bank_code">Código de Banco</Label>
|
||||
<Input
|
||||
id="bank_code"
|
||||
type="number"
|
||||
bind:value={formData.bank_code}
|
||||
placeholder="Código numérico"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Cajero -->
|
||||
<div class="space-y-2">
|
||||
<Label for="cashier">Cajero</Label>
|
||||
<Input
|
||||
id="cashier"
|
||||
bind:value={formData.cashier}
|
||||
placeholder="Máx. 2 caracteres"
|
||||
maxlength={2}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Fecha -->
|
||||
<div class="space-y-2">
|
||||
<Label for="date">Fecha</Label>
|
||||
<Input
|
||||
id="date"
|
||||
type="date"
|
||||
bind:value={formData.date}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Hora -->
|
||||
<div class="space-y-2">
|
||||
<Label for="time">Hora</Label>
|
||||
<Input
|
||||
id="time"
|
||||
type="time"
|
||||
bind:value={formData.time}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Turno -->
|
||||
<div class="space-y-2">
|
||||
<Label for="shift">Turno</Label>
|
||||
<Input
|
||||
id="shift"
|
||||
bind:value={formData.shift}
|
||||
placeholder="1 carácter"
|
||||
maxlength={1}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Total Efectivo Pagado -->
|
||||
<div class="space-y-2">
|
||||
<Label for="total_cash_paid">Total Efectivo Pagado</Label>
|
||||
<Input
|
||||
id="total_cash_paid"
|
||||
type="number"
|
||||
bind:value={formData.total_cash_paid}
|
||||
placeholder="Monto en efectivo"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Total Contribuciones -->
|
||||
<div class="space-y-2">
|
||||
<Label for="total_contributions">Total Contribuciones</Label>
|
||||
<Input
|
||||
id="total_contributions"
|
||||
type="number"
|
||||
bind:value={formData.total_contributions}
|
||||
placeholder="Monto total"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Pago en Ventanilla -->
|
||||
<div class="space-y-2">
|
||||
<Label for="counter_payment">Pago en Ventanilla</Label>
|
||||
<Input
|
||||
id="counter_payment"
|
||||
type="number"
|
||||
bind:value={formData.counter_payment}
|
||||
placeholder="Monto"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Código PECE -->
|
||||
<div class="space-y-2">
|
||||
<Label for="pece_code">Código PECE</Label>
|
||||
<Input
|
||||
id="pece_code"
|
||||
bind:value={formData.pece_code}
|
||||
placeholder="Máx. 5 caracteres"
|
||||
maxlength={5}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- ID de Pago -->
|
||||
<div class="space-y-2">
|
||||
<Label for="payment_id">ID de Pago</Label>
|
||||
<Input
|
||||
id="payment_id"
|
||||
type="number"
|
||||
bind:value={formData.payment_id}
|
||||
placeholder="Identificador"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-6">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<!-- Acuse -->
|
||||
<div class="space-y-2">
|
||||
<Label for="acknowledgment">Acuse</Label>
|
||||
<Input
|
||||
id="acknowledgment"
|
||||
bind:value={formData.acknowledgment}
|
||||
maxlength={20}
|
||||
placeholder="Acuse"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Número de Operación -->
|
||||
<div class="space-y-2">
|
||||
<Label for="operation_number">Número de Operación</Label>
|
||||
<Input
|
||||
id="operation_number"
|
||||
bind:value={formData.operation_number}
|
||||
maxlength={14}
|
||||
placeholder="Número de operación"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Código Bancario -->
|
||||
<div class="space-y-2">
|
||||
<Label for="bank_code">Código Bancario</Label>
|
||||
<Input
|
||||
id="bank_code"
|
||||
type="number"
|
||||
bind:value={formData.bank_code}
|
||||
placeholder="Código del banco"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Cajero -->
|
||||
<div class="space-y-2">
|
||||
<Label for="cashier">Cajero</Label>
|
||||
<Input
|
||||
id="cashier"
|
||||
bind:value={formData.cashier}
|
||||
maxlength={2}
|
||||
placeholder="Cajero"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Fecha -->
|
||||
<div class="space-y-2">
|
||||
<Label for="date">Fecha</Label>
|
||||
<Input
|
||||
id="date"
|
||||
type="date"
|
||||
bind:value={formData.date}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Hora -->
|
||||
<div class="space-y-2">
|
||||
<Label for="time">Hora</Label>
|
||||
<Input
|
||||
id="time"
|
||||
type="time"
|
||||
bind:value={formData.time}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Turno -->
|
||||
<div class="space-y-2">
|
||||
<Label for="shift">Turno</Label>
|
||||
<Input
|
||||
id="shift"
|
||||
bind:value={formData.shift}
|
||||
maxlength={1}
|
||||
placeholder="Turno"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Total Pagado en Efectivo -->
|
||||
<div class="space-y-2">
|
||||
<Label for="total_cash_paid">Total Pagado en Efectivo</Label>
|
||||
<Input
|
||||
id="total_cash_paid"
|
||||
type="number"
|
||||
bind:value={formData.total_cash_paid}
|
||||
placeholder="Total en efectivo"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Total Contribuciones -->
|
||||
<div class="space-y-2">
|
||||
<Label for="total_contributions">Total Contribuciones</Label>
|
||||
<Input
|
||||
id="total_contributions"
|
||||
type="number"
|
||||
bind:value={formData.total_contributions}
|
||||
placeholder="Total contribuciones"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Pago en Mostrador -->
|
||||
<div class="space-y-2">
|
||||
<Label for="counter_payment">Pago en Mostrador</Label>
|
||||
<Input
|
||||
id="counter_payment"
|
||||
type="number"
|
||||
bind:value={formData.counter_payment}
|
||||
placeholder="Pago en mostrador"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Código PECE -->
|
||||
<div class="space-y-2">
|
||||
<Label for="pece_code">Código PECE</Label>
|
||||
<Input
|
||||
id="pece_code"
|
||||
bind:value={formData.pece_code}
|
||||
maxlength={5}
|
||||
placeholder="Código PECE"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
@@ -1,74 +1,40 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import { Skeleton } from '$lib/components/ui/skeleton';
|
||||
import { pedimentoTransportApi } from '$lib/api/dashboard/a76/pedimento-transport';
|
||||
import type { Pedimento } from '$lib/api/dashboard/a76/pedimentos';
|
||||
|
||||
let {
|
||||
pedimentoId,
|
||||
pedimento,
|
||||
formData = $bindable(),
|
||||
exists = $bindable()
|
||||
}: {
|
||||
pedimentoId: number | null;
|
||||
pedimento: Pedimento | null;
|
||||
formData?: any;
|
||||
exists?: boolean;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(true);
|
||||
|
||||
onMount(async () => {
|
||||
await loadTransport();
|
||||
});
|
||||
|
||||
async function loadTransport() {
|
||||
// Si no hay pedimentoId (modo creación), inicializar vacío
|
||||
if (!pedimentoId) {
|
||||
// Inicializar formData inmediatamente
|
||||
const transportData = pedimento?.pedimento_transport_means;
|
||||
if (transportData) {
|
||||
exists = true;
|
||||
if (!formData) {
|
||||
formData = {
|
||||
destination: null,
|
||||
entry_exit: '',
|
||||
arrival: '',
|
||||
departure: ''
|
||||
destination: transportData.destination || '',
|
||||
entry_exit: transportData.entry_exit || '',
|
||||
arrival: transportData.arrival || '',
|
||||
departure: transportData.departure || ''
|
||||
};
|
||||
exists = false;
|
||||
loading = false;
|
||||
return;
|
||||
}
|
||||
loading = true;
|
||||
|
||||
try {
|
||||
const response = await pedimentoTransportApi.get(pedimentoId);
|
||||
|
||||
if (response.error) {
|
||||
// No existe o hay error - inicializar vacío
|
||||
exists = false;
|
||||
formData = {
|
||||
destination: null,
|
||||
entry_exit: '',
|
||||
arrival: '',
|
||||
departure: ''
|
||||
};
|
||||
} else if (response.data) {
|
||||
exists = true;
|
||||
formData = {
|
||||
destination: response.data.destination || null,
|
||||
entry_exit: response.data.entry_exit || '',
|
||||
arrival: response.data.arrival || '',
|
||||
departure: response.data.departure || ''
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error loading transport:', e);
|
||||
exists = false;
|
||||
} else {
|
||||
exists = false;
|
||||
if (!formData) {
|
||||
formData = {
|
||||
destination: null,
|
||||
destination: '',
|
||||
entry_exit: '',
|
||||
arrival: '',
|
||||
departure: ''
|
||||
};
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -81,61 +47,52 @@
|
||||
</Card.Description>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
{#if loading}
|
||||
<div class="space-y-4">
|
||||
<Skeleton class="h-10 w-full" />
|
||||
<Skeleton class="h-10 w-full" />
|
||||
<Skeleton class="h-10 w-full" />
|
||||
<Skeleton class="h-10 w-full" />
|
||||
</div>
|
||||
{:else}
|
||||
<div class="space-y-6">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<!-- Destino -->
|
||||
<div class="space-y-2">
|
||||
<Label for="destination">Destino</Label>
|
||||
<Input
|
||||
id="destination"
|
||||
type="number"
|
||||
bind:value={formData.destination}
|
||||
placeholder="Código de destino"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-6">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<!-- Destino -->
|
||||
<div class="space-y-2">
|
||||
<Label for="destination">Destino</Label>
|
||||
<Input
|
||||
id="destination"
|
||||
type="number"
|
||||
bind:value={formData.destination}
|
||||
placeholder="Destino"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Entrada/Salida -->
|
||||
<div class="space-y-2">
|
||||
<Label for="entry_exit">Entrada/Salida</Label>
|
||||
<Input
|
||||
id="entry_exit"
|
||||
bind:value={formData.entry_exit}
|
||||
placeholder="Máx. 2 caracteres"
|
||||
maxlength={2}
|
||||
/>
|
||||
</div>
|
||||
<!-- Entrada/Salida -->
|
||||
<div class="space-y-2">
|
||||
<Label for="entry_exit">Entrada/Salida</Label>
|
||||
<Input
|
||||
id="entry_exit"
|
||||
bind:value={formData.entry_exit}
|
||||
maxlength={2}
|
||||
placeholder="Entrada/Salida"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Llegada -->
|
||||
<div class="space-y-2">
|
||||
<Label for="arrival">Llegada</Label>
|
||||
<Input
|
||||
id="arrival"
|
||||
bind:value={formData.arrival}
|
||||
placeholder="Máx. 2 caracteres"
|
||||
maxlength={2}
|
||||
/>
|
||||
</div>
|
||||
<!-- Llegada -->
|
||||
<div class="space-y-2">
|
||||
<Label for="arrival">Llegada</Label>
|
||||
<Input
|
||||
id="arrival"
|
||||
bind:value={formData.arrival}
|
||||
maxlength={2}
|
||||
placeholder="Llegada"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Salida -->
|
||||
<div class="space-y-2">
|
||||
<Label for="departure">Salida</Label>
|
||||
<Input
|
||||
id="departure"
|
||||
bind:value={formData.departure}
|
||||
placeholder="Máx. 2 caracteres"
|
||||
maxlength={2}
|
||||
/>
|
||||
</div>
|
||||
<!-- Salida -->
|
||||
<div class="space-y-2">
|
||||
<Label for="departure">Salida</Label>
|
||||
<Input
|
||||
id="departure"
|
||||
bind:value={formData.departure}
|
||||
maxlength={2}
|
||||
placeholder="Salida"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
@@ -1,79 +1,39 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import { Textarea } from '$lib/components/ui/textarea';
|
||||
import { Skeleton } from '$lib/components/ui/skeleton';
|
||||
import { pedimentoValidationApi } from '$lib/api/dashboard/a76/pedimento-validation';
|
||||
import type { Pedimento } from '$lib/api/dashboard/a76/pedimentos';
|
||||
|
||||
let {
|
||||
pedimentoId,
|
||||
pedimento,
|
||||
formData = $bindable(),
|
||||
exists = $bindable()
|
||||
}: {
|
||||
pedimentoId: number | null;
|
||||
pedimento: Pedimento | null;
|
||||
formData?: any;
|
||||
exists?: boolean;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(true);
|
||||
|
||||
onMount(async () => {
|
||||
await loadValidation();
|
||||
});
|
||||
|
||||
async function loadValidation() {
|
||||
// Si no hay pedimentoId (modo creación), inicializar vacío
|
||||
if (!pedimentoId) {
|
||||
// Inicializar formData inmediatamente
|
||||
const validationData = pedimento?.pedimento_validation;
|
||||
if (validationData) {
|
||||
exists = true;
|
||||
if (!formData) {
|
||||
formData = {
|
||||
validator: '',
|
||||
validation_ack: '',
|
||||
pre_ack: '',
|
||||
line_signature: '',
|
||||
electronic_signature: '',
|
||||
certificate_number: '',
|
||||
validator_id: null,
|
||||
responsible_id: null
|
||||
validator: validationData.validator || '',
|
||||
validation_ack: validationData.validation_ack || '',
|
||||
pre_ack: validationData.pre_ack || '',
|
||||
line_signature: validationData.line_signature || '',
|
||||
electronic_signature: validationData.electronic_signature || '',
|
||||
certificate_number: validationData.certificate_number || '',
|
||||
validator_id: validationData.validator_id || '',
|
||||
responsible_id: validationData.responsible_id || ''
|
||||
};
|
||||
exists = false;
|
||||
loading = false;
|
||||
return;
|
||||
}
|
||||
loading = true;
|
||||
|
||||
try {
|
||||
const response = await pedimentoValidationApi.get(pedimentoId);
|
||||
|
||||
if (response.error) {
|
||||
// No existe o hay error - inicializar vacío
|
||||
exists = false;
|
||||
formData = {
|
||||
validator: '',
|
||||
validation_ack: '',
|
||||
pre_ack: '',
|
||||
line_signature: '',
|
||||
electronic_signature: '',
|
||||
certificate_number: '',
|
||||
validator_id: null,
|
||||
responsible_id: null
|
||||
};
|
||||
} else if (response.data) {
|
||||
exists = true;
|
||||
formData = {
|
||||
validator: response.data.validator || '',
|
||||
validation_ack: response.data.validation_ack || '',
|
||||
pre_ack: response.data.pre_ack || '',
|
||||
line_signature: response.data.line_signature || '',
|
||||
electronic_signature: response.data.electronic_signature || '',
|
||||
certificate_number: response.data.certificate_number || '',
|
||||
validator_id: response.data.validator_id || null,
|
||||
responsible_id: response.data.responsible_id || null
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error loading validation:', e);
|
||||
exists = false;
|
||||
} else {
|
||||
exists = false;
|
||||
if (!formData) {
|
||||
formData = {
|
||||
validator: '',
|
||||
validation_ack: '',
|
||||
@@ -81,11 +41,9 @@
|
||||
line_signature: '',
|
||||
electronic_signature: '',
|
||||
certificate_number: '',
|
||||
validator_id: null,
|
||||
responsible_id: null
|
||||
validator_id: '',
|
||||
responsible_id: ''
|
||||
};
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -98,110 +56,96 @@
|
||||
</Card.Description>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
{#if loading}
|
||||
<div class="space-y-4">
|
||||
<Skeleton class="h-10 w-full" />
|
||||
<Skeleton class="h-10 w-full" />
|
||||
<Skeleton class="h-32 w-full" />
|
||||
<Skeleton class="h-32 w-full" />
|
||||
</div>
|
||||
{:else}
|
||||
<div class="space-y-6">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<!-- Validador -->
|
||||
<div class="space-y-2">
|
||||
<Label for="validator">Validador</Label>
|
||||
<Input
|
||||
id="validator"
|
||||
bind:value={formData.validator}
|
||||
placeholder="Máx. 3 caracteres"
|
||||
maxlength={3}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Acuse de Validación -->
|
||||
<div class="space-y-2">
|
||||
<Label for="validation_ack">Acuse de Validación</Label>
|
||||
<Input
|
||||
id="validation_ack"
|
||||
bind:value={formData.validation_ack}
|
||||
placeholder="Máx. 8 caracteres"
|
||||
maxlength={8}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Pre-acuse -->
|
||||
<div class="space-y-2">
|
||||
<Label for="pre_ack">Pre-acuse</Label>
|
||||
<Input
|
||||
id="pre_ack"
|
||||
bind:value={formData.pre_ack}
|
||||
placeholder="Máx. 8 caracteres"
|
||||
maxlength={8}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Número de Certificado -->
|
||||
<div class="space-y-2">
|
||||
<Label for="certificate_number">Número de Certificado</Label>
|
||||
<Input
|
||||
id="certificate_number"
|
||||
bind:value={formData.certificate_number}
|
||||
placeholder="Máx. 99 caracteres"
|
||||
maxlength={99}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- ID de Validador -->
|
||||
<div class="space-y-2">
|
||||
<Label for="validator_id">ID de Validador</Label>
|
||||
<Input
|
||||
id="validator_id"
|
||||
type="number"
|
||||
bind:value={formData.validator_id}
|
||||
placeholder="Identificador numérico"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- ID de Responsable -->
|
||||
<div class="space-y-2">
|
||||
<Label for="responsible_id">ID de Responsable</Label>
|
||||
<Input
|
||||
id="responsible_id"
|
||||
type="number"
|
||||
bind:value={formData.responsible_id}
|
||||
placeholder="Identificador numérico"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-6">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<!-- Validador -->
|
||||
<div class="space-y-2">
|
||||
<Label for="validator">Validador</Label>
|
||||
<Input
|
||||
id="validator"
|
||||
bind:value={formData.validator}
|
||||
maxlength={3}
|
||||
placeholder="Validador"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Firma de Línea -->
|
||||
<!-- Acuse de Validación -->
|
||||
<div class="space-y-2">
|
||||
<Label for="line_signature">Firma de Línea</Label>
|
||||
<Label for="validation_ack">Acuse de Validación</Label>
|
||||
<Input
|
||||
id="validation_ack"
|
||||
bind:value={formData.validation_ack}
|
||||
maxlength={8}
|
||||
placeholder="Acuse de validación"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Acuse Previo -->
|
||||
<div class="space-y-2">
|
||||
<Label for="pre_ack">Acuse Previo</Label>
|
||||
<Input
|
||||
id="pre_ack"
|
||||
bind:value={formData.pre_ack}
|
||||
maxlength={8}
|
||||
placeholder="Acuse previo"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Firma Línea de Captura -->
|
||||
<div class="space-y-2">
|
||||
<Label for="line_signature">Firma Línea de Captura</Label>
|
||||
<Input
|
||||
id="line_signature"
|
||||
bind:value={formData.line_signature}
|
||||
placeholder="Máx. 50 caracteres"
|
||||
maxlength={50}
|
||||
placeholder="Firma línea de captura"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Firma Electrónica -->
|
||||
<div class="space-y-2">
|
||||
<div class="space-y-2 md:col-span-2">
|
||||
<Label for="electronic_signature">Firma Electrónica</Label>
|
||||
<Textarea
|
||||
<Input
|
||||
id="electronic_signature"
|
||||
bind:value={formData.electronic_signature}
|
||||
placeholder="Ingresa la firma electrónica..."
|
||||
rows={6}
|
||||
class="resize-none font-mono text-sm"
|
||||
maxlength={999}
|
||||
placeholder="Firma electrónica"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Número de Certificado -->
|
||||
<div class="space-y-2">
|
||||
<Label for="certificate_number">Número de Certificado</Label>
|
||||
<Input
|
||||
id="certificate_number"
|
||||
bind:value={formData.certificate_number}
|
||||
maxlength={99}
|
||||
placeholder="Número de certificado"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- ID de Validador -->
|
||||
<div class="space-y-2">
|
||||
<Label for="validator_id">ID de Validador</Label>
|
||||
<Input
|
||||
id="validator_id"
|
||||
type="number"
|
||||
bind:value={formData.validator_id}
|
||||
placeholder="ID de validador"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- ID de Responsable -->
|
||||
<div class="space-y-2">
|
||||
<Label for="responsible_id">ID de Responsable</Label>
|
||||
<Input
|
||||
id="responsible_id"
|
||||
type="number"
|
||||
bind:value={formData.responsible_id}
|
||||
placeholder="ID de responsable"
|
||||
/>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
Firma electrónica del pedimento (máximo 999 caracteres).
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as AlertDialog from "$lib/components/ui/alert-dialog";
|
||||
import { codePedimentoRegimensApi, type CodePedimentoRegimen } from "$lib/api/dashboard/refrence_data/code_pedimento_regimens";
|
||||
import { LoaderCircle } from 'lucide-svelte';
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as AlertDialog from "$lib/components/ui/alert-dialog";
|
||||
import { containersApi, type Container } from "$lib/api/dashboard/refrence_data/containers";
|
||||
import { LoaderCircle } from 'lucide-svelte';
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
@@ -84,26 +85,7 @@
|
||||
class="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
>
|
||||
{#if loading}
|
||||
<svg
|
||||
class="mr-2 h-4 w-4 animate-spin"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle
|
||||
class="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
stroke-width="4"
|
||||
></circle>
|
||||
<path
|
||||
class="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path>
|
||||
</svg>
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</AlertDialog.Action>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as AlertDialog from "$lib/components/ui/alert-dialog";
|
||||
import { customsSectionsApi, type CustomsSection } from "$lib/api/dashboard/refrence_data/customs_sections";
|
||||
import { LoaderCircle } from 'lucide-svelte';
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as AlertDialog from "$lib/components/ui/alert-dialog";
|
||||
import { customsWarehousesApi, type CustomsWarehouse } from "$lib/api/dashboard/refrence_data/customs_warehouses";
|
||||
import { LoaderCircle } from 'lucide-svelte';
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as AlertDialog from "$lib/components/ui/alert-dialog";
|
||||
import { materialTypesApi, type MaterialType } from "$lib/api/dashboard/refrence_data/material_types";
|
||||
import { LoaderCircle } from 'lucide-svelte';
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
@@ -88,26 +89,7 @@
|
||||
class="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
>
|
||||
{#if loading}
|
||||
<svg
|
||||
class="mr-2 h-4 w-4 animate-spin"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle
|
||||
class="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
stroke-width="4"
|
||||
></circle>
|
||||
<path
|
||||
class="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path>
|
||||
</svg>
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</AlertDialog.Action>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as AlertDialog from "$lib/components/ui/alert-dialog";
|
||||
import { paymentMethodsApi, type PaymentMethod } from "$lib/api/dashboard/refrence_data/payment_methods";
|
||||
import { LoaderCircle } from 'lucide-svelte';
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
@@ -84,26 +85,7 @@
|
||||
class="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
>
|
||||
{#if loading}
|
||||
<svg
|
||||
class="mr-2 h-4 w-4 animate-spin"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle
|
||||
class="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
stroke-width="4"
|
||||
></circle>
|
||||
<path
|
||||
class="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path>
|
||||
</svg>
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</AlertDialog.Action>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as AlertDialog from "$lib/components/ui/alert-dialog";
|
||||
import { pedimentoRegimensApi, type PedimentoRegimen } from "$lib/api/dashboard/refrence_data/pedimento_regimens";
|
||||
import { LoaderCircle } from 'lucide-svelte';
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
@@ -84,26 +85,7 @@
|
||||
class="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
>
|
||||
{#if loading}
|
||||
<svg
|
||||
class="mr-2 h-4 w-4 animate-spin"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle
|
||||
class="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
stroke-width="4"
|
||||
></circle>
|
||||
<path
|
||||
class="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path>
|
||||
</svg>
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</AlertDialog.Action>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import { statesApi, type State, type CreateStateData, type UpdateStateData } from "$lib/api/dashboard/refrence_data/states";
|
||||
import { LoaderCircle } from 'lucide-svelte';
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
@@ -195,26 +196,7 @@
|
||||
</Button>
|
||||
<Button type="submit" disabled={loading}>
|
||||
{#if loading}
|
||||
<svg
|
||||
class="mr-2 h-4 w-4 animate-spin"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle
|
||||
class="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
stroke-width="4"
|
||||
></circle>
|
||||
<path
|
||||
class="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path>
|
||||
</svg>
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{/if}
|
||||
{isEditing ? "Guardar cambios" : "Crear"}
|
||||
</Button>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as AlertDialog from "$lib/components/ui/alert-dialog";
|
||||
import { statesApi, type State } from "$lib/api/dashboard/refrence_data/states";
|
||||
import { LoaderCircle } from 'lucide-svelte';
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
@@ -96,26 +97,7 @@
|
||||
class="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
>
|
||||
{#if loading}
|
||||
<svg
|
||||
class="mr-2 h-4 w-4 animate-spin"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle
|
||||
class="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
stroke-width="4"
|
||||
></circle>
|
||||
<path
|
||||
class="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path>
|
||||
</svg>
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</AlertDialog.Action>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as AlertDialog from "$lib/components/ui/alert-dialog";
|
||||
import { transportModesApi, type TransportMode } from "$lib/api/dashboard/refrence_data/transport_modes";
|
||||
import { LoaderCircle } from 'lucide-svelte';
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
@@ -84,26 +85,7 @@
|
||||
class="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
>
|
||||
{#if loading}
|
||||
<svg
|
||||
class="mr-2 h-4 w-4 animate-spin"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle
|
||||
class="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
stroke-width="4"
|
||||
></circle>
|
||||
<path
|
||||
class="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path>
|
||||
</svg>
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</AlertDialog.Action>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import { Input } from "$lib/components/ui/input/index.js";
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { FileText, ShieldCheck } from 'lucide-svelte';
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { page } from '$app/state';
|
||||
import { enhance } from '$app/forms';
|
||||
@@ -203,14 +204,10 @@
|
||||
<div class="relative w-full h-full flex flex-col items-center justify-center gap-8">
|
||||
<!-- Ícono principal - Escudo con documento -->
|
||||
<div class="relative">
|
||||
<svg class="w-48 h-48 text-blue-600 dark:text-blue-400 opacity-90" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
||||
</svg>
|
||||
<FileText class="w-48 h-48 text-blue-600 dark:text-blue-400 opacity-90" strokeWidth={1.5} />
|
||||
<!-- Escudo de protección superpuesto -->
|
||||
<div class="absolute -bottom-2 -right-2 bg-white dark:bg-slate-800 rounded-full p-2 shadow-lg">
|
||||
<svg class="w-12 h-12 text-green-600 dark:text-green-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" d="M2.166 4.999A11.954 11.954 0 0010 1.944 11.954 11.954 0 0017.834 5c.11.65.166 1.32.166 2.001 0 5.225-3.34 9.67-8 11.317C5.34 16.67 2 12.225 2 7c0-.682.057-1.35.166-2.001zm11.541 3.708a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
<ShieldCheck class="w-12 h-12 text-green-600 dark:text-green-400" fill="currentColor" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ class CompanyStore {
|
||||
// Si no hay datos pre-cargados, hacer fetch (fallback)
|
||||
this._loading = true;
|
||||
try {
|
||||
const response = await fetch('/api/company/my-companies');
|
||||
const response = await fetch('/api/v1/a76/company/my-companies');
|
||||
if (response.ok) {
|
||||
const newCompanies = await response.json();
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import * as Card from "$lib/components/ui/card";
|
||||
import { FileText, LayoutGrid, Package } from 'lucide-svelte';
|
||||
</script>
|
||||
|
||||
<div class="space-y-6">
|
||||
@@ -52,78 +53,27 @@
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
<a
|
||||
href="/dashboard/reference_data/code_pedimento_regimens"
|
||||
class="flex flex-col items-center justify-center rounded-lg border p-6 hover:bg-accent transition-colors"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mb-2"
|
||||
>
|
||||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
|
||||
<polyline points="14 2 14 8 20 8" />
|
||||
<line x1="16" x2="8" y1="13" y2="13" />
|
||||
<line x1="16" x2="8" y1="17" y2="17" />
|
||||
<polyline points="10 9 9 9 8 9" />
|
||||
</svg>
|
||||
<span class="font-medium">Código Pedimento - Regímenes</span>
|
||||
<span class="text-xs text-muted-foreground">Gestionar relaciones</span>
|
||||
</a>
|
||||
|
||||
<div
|
||||
class="flex flex-col items-center justify-center rounded-lg border p-6 opacity-50 cursor-not-allowed"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mb-2"
|
||||
>
|
||||
<rect width="18" height="18" x="3" y="3" rx="2" ry="2" />
|
||||
<line x1="3" x2="21" y1="9" y2="9" />
|
||||
<line x1="9" x2="9" y1="21" y2="9" />
|
||||
</svg>
|
||||
<span class="font-medium">Catálogo de Tipos</span>
|
||||
<span class="text-xs text-muted-foreground">Próximamente</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex flex-col items-center justify-center rounded-lg border p-6 opacity-50 cursor-not-allowed"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mb-2"
|
||||
>
|
||||
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z" />
|
||||
<polyline points="3.27 6.96 12 12.01 20.73 6.96" />
|
||||
<line x1="12" x2="12" y1="22.08" y2="12" />
|
||||
</svg>
|
||||
<span class="font-medium">Reportes</span>
|
||||
<span class="text-xs text-muted-foreground">Próximamente</span>
|
||||
</div>
|
||||
<a
|
||||
href="/dashboard/reference_data/code_pedimento_regimens"
|
||||
class="flex flex-col items-center justify-center rounded-lg border p-6 hover:bg-accent transition-colors"
|
||||
>
|
||||
<FileText class="mb-2" size={24} />
|
||||
<span class="font-medium">Código Pedimento - Regímenes</span>
|
||||
<span class="text-xs text-muted-foreground">Gestionar relaciones</span>
|
||||
</a> <div
|
||||
class="flex flex-col items-center justify-center rounded-lg border p-6 opacity-50 cursor-not-allowed"
|
||||
>
|
||||
<LayoutGrid class="mb-2" size={24} />
|
||||
<span class="font-medium">Catálogo de Tipos</span>
|
||||
<span class="text-xs text-muted-foreground">Próximamente</span>
|
||||
</div> <div
|
||||
class="flex flex-col items-center justify-center rounded-lg border p-6 opacity-50 cursor-not-allowed"
|
||||
>
|
||||
<Package class="mb-2" size={24} />
|
||||
<span class="font-medium">Reportes</span>
|
||||
<span class="text-xs text-muted-foreground">Próximamente</span>
|
||||
</div>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
</div>
|
||||
</div>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
</div>
|
||||
@@ -4,9 +4,9 @@ import { getAuthTokens, authenticatedFetch } from '$lib/server/api';
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
// Esperar a que el layout padre valide/refresque el token
|
||||
const parentData = await parent();
|
||||
|
||||
|
||||
const { accessToken } = getAuthTokens(cookies);
|
||||
|
||||
|
||||
if (!accessToken) {
|
||||
return {
|
||||
error: 'No authenticated',
|
||||
@@ -22,20 +22,20 @@ export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
// Obtener parámetros de paginación de la URL
|
||||
const page = parseInt(url.searchParams.get('page') || '1');
|
||||
const pageSize = parseInt(url.searchParams.get('page_size') || '50');
|
||||
|
||||
|
||||
// Obtener company_id de múltiples fuentes (en orden de prioridad):
|
||||
// 1. URL query param (permite cambiar vía navegación)
|
||||
// 2. Cookie active_company_id (setted por el team-switcher)
|
||||
// 3. Primera compañía del usuario (fallback)
|
||||
const companyIdParam = url.searchParams.get('company_id');
|
||||
const cookieCompanyId = cookies.get('active_company_id');
|
||||
|
||||
const companyId = companyIdParam
|
||||
|
||||
const companyId = companyIdParam
|
||||
? parseInt(companyIdParam)
|
||||
: cookieCompanyId
|
||||
? parseInt(cookieCompanyId)
|
||||
: parentData.companies?.[0]?.id;
|
||||
|
||||
|
||||
if (!companyId) {
|
||||
return {
|
||||
error: 'No company selected',
|
||||
@@ -47,9 +47,17 @@ export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
};
|
||||
}
|
||||
|
||||
// Construir URL con parámetros
|
||||
let apiUrl = `v1/a76/clients-providers?company_id=${companyId}&page=${page}&page_size=${pageSize}`;
|
||||
|
||||
const type = url.searchParams.get('type');
|
||||
if (type && type !== 'both') {
|
||||
apiUrl += `&type=${type}`;
|
||||
}
|
||||
|
||||
// Usar authenticatedFetch para manejar automáticamente el refresh de tokens
|
||||
const response = await authenticatedFetch(
|
||||
`v1/a76/clients-providers?company_id=${companyId}&page=${page}&page_size=${pageSize}`,
|
||||
apiUrl,
|
||||
{},
|
||||
cookies,
|
||||
fetch
|
||||
@@ -62,7 +70,7 @@ export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
statusText: response.statusText,
|
||||
error: errorText
|
||||
});
|
||||
|
||||
|
||||
return {
|
||||
error: `Error ${response.status}: ${response.statusText}`,
|
||||
items: [],
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
import CreateEditDialog from '$lib/components/dashboard/clients_and_providers/create-edit-dialog.svelte';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Select from '$lib/components/ui/select';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
import { page } from '$app/stores';
|
||||
import { goto } from '$app/navigation';
|
||||
import type { PageData } from './$types';
|
||||
import { browser } from '$app/environment';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
@@ -16,6 +20,22 @@
|
||||
// Estado para el diálogo de crear
|
||||
let showCreateDialog = $state(false);
|
||||
|
||||
// Estado para el filtro de tipo
|
||||
let selectedType = $state<string>($page.url.searchParams.get('type') || 'both');
|
||||
|
||||
// Actualizar URL cuando cambia el filtro
|
||||
function handleTypeChange(value: string) {
|
||||
selectedType = value;
|
||||
const url = new URL(window.location.href);
|
||||
if (value === 'both') {
|
||||
url.searchParams.delete('type');
|
||||
} else {
|
||||
url.searchParams.set('type', value);
|
||||
}
|
||||
goto(url.toString(), { keepFocus: true, noScroll: true, replaceState: true });
|
||||
reloadData();
|
||||
}
|
||||
|
||||
// Sincronizar token de cookies a localStorage al montar el componente
|
||||
onMount(() => {
|
||||
if (browser) {
|
||||
@@ -77,7 +97,8 @@
|
||||
const response = await clientsProvidersApi.list(
|
||||
companyStore.activeCompany.id,
|
||||
currentPage + 1,
|
||||
pageSize
|
||||
pageSize,
|
||||
selectedType !== 'both' ? { type: selectedType } : undefined
|
||||
);
|
||||
|
||||
if (response.error) {
|
||||
@@ -121,7 +142,8 @@
|
||||
const response = await clientsProvidersApi.list(
|
||||
companyStore.activeCompany.id,
|
||||
1,
|
||||
pageSize
|
||||
pageSize,
|
||||
selectedType !== 'both' ? { type: selectedType } : undefined
|
||||
);
|
||||
|
||||
if (response.error) {
|
||||
@@ -174,21 +196,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={handleCreateClick}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M5 12h14" />
|
||||
<path d="M12 5v14" />
|
||||
</svg>
|
||||
<Plus class="mr-2" size={16} />
|
||||
Nuevo Cliente/Proveedor
|
||||
</Button>
|
||||
</div>
|
||||
@@ -216,25 +224,23 @@
|
||||
{/if}
|
||||
</Card.Description>
|
||||
</div>
|
||||
<Button variant="outline" onclick={reloadData}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
|
||||
<path d="M21 3v5h-5" />
|
||||
</svg>
|
||||
<div class="flex items-center gap-2">
|
||||
<Select.Root type="single" value={selectedType} onValueChange={handleTypeChange}>
|
||||
<Select.Trigger class="w-[180px]">
|
||||
{selectedType === 'both' ? 'Todos' : selectedType === 'client' ? 'Clientes' : 'Proveedores'}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
<Select.Item value="both">Todos</Select.Item>
|
||||
<Select.Item value="client">Clientes</Select.Item>
|
||||
<Select.Item value="provider">Proveedores</Select.Item>
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
<Button variant="outline" onclick={reloadData}>
|
||||
<RefreshCw class="mr-2" size={16} />
|
||||
Actualizar
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<!-- TanStack DataTable con Infinite Scroll -->
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
import type { PageData } from './$types';
|
||||
import { browser } from '$app/environment';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import { Plus, Search, Trash2 } from 'lucide-svelte';
|
||||
|
||||
// Los datos iniciales vienen del servidor
|
||||
let { data }: { data: PageData } = $props();
|
||||
@@ -175,21 +176,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={handleCreateClick}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M5 12h14" />
|
||||
<path d="M12 5v14" />
|
||||
</svg>
|
||||
<Plus class="mr-2" size={16} />
|
||||
Nuevo Agente Aduanal
|
||||
</Button>
|
||||
</div>
|
||||
@@ -226,44 +213,15 @@
|
||||
Buscando...
|
||||
</div>
|
||||
{:else}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<circle cx="11" cy="11" r="8" />
|
||||
<path d="m21 21-4.3-4.3" />
|
||||
</svg>
|
||||
<Search class="mr-2" size={16} />
|
||||
Buscar
|
||||
{/if}
|
||||
</Button>
|
||||
{#if searchedBroker}
|
||||
<Button type="button" variant="outline" onclick={reloadData}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M3 6h18" />
|
||||
<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" />
|
||||
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" />
|
||||
</svg>
|
||||
Limpiar
|
||||
</Button>
|
||||
{#if searchedBroker}
|
||||
<Button type="button" variant="outline" onclick={reloadData}>
|
||||
<Trash2 class="mr-2" size={16} />
|
||||
Limpiar
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import { Plus, Filter, Trash2, RefreshCw } from 'lucide-svelte';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
// Estado para la lista de classes
|
||||
@@ -255,21 +256,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={handleCreateClick}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M5 12h14" />
|
||||
<path d="M12 5v14" />
|
||||
</svg>
|
||||
<Plus class="mr-2" size={16} />
|
||||
Nueva Clase
|
||||
</Button>
|
||||
</div>
|
||||
@@ -305,38 +292,11 @@
|
||||
|
||||
<div class="flex items-end gap-2 md:col-span-2">
|
||||
<Button type="submit" disabled={loading} class="flex-1">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3" />
|
||||
</svg>
|
||||
<Filter class="mr-2" size={16} />
|
||||
Filtrar
|
||||
</Button>
|
||||
<Button type="button" variant="outline" onclick={clearFilters} disabled={loading}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M3 6h18" />
|
||||
<path d="m19 6-2 14a2 2 0 0 1-2 2H9a2 2 0 0 1-2-2L5 6" />
|
||||
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" />
|
||||
</svg>
|
||||
<Trash2 size={16} />
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -364,21 +324,7 @@
|
||||
</Card.Description>
|
||||
</div>
|
||||
<Button variant="outline" onclick={reloadData}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
|
||||
<path d="M21 3v5h-5" />
|
||||
</svg>
|
||||
<RefreshCw class="mr-2" size={16} />
|
||||
Actualizar
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import type { PageData } from './$types';
|
||||
import { browser } from '$app/environment';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import { Plus, Filter, Trash2, RefreshCw } from 'lucide-svelte';
|
||||
|
||||
// Los datos iniciales vienen del servidor
|
||||
let { data }: { data: PageData } = $props();
|
||||
@@ -260,21 +261,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={handleCreateClick}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M5 12h14" />
|
||||
<path d="M12 5v14" />
|
||||
</svg>
|
||||
<Plus class="mr-2" size={16} />
|
||||
Nuevo Pedimento
|
||||
</Button>
|
||||
</div>
|
||||
@@ -322,38 +309,11 @@
|
||||
|
||||
<div class="flex items-end gap-2">
|
||||
<Button type="submit" disabled={loading} class="flex-1">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3" />
|
||||
</svg>
|
||||
<Filter class="mr-2" size={16} />
|
||||
Filtrar
|
||||
</Button>
|
||||
<Button type="button" variant="outline" onclick={clearFilters} disabled={loading}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M3 6h18" />
|
||||
<path d="m19 6-2 14a2 2 0 0 1-2 2H9a2 2 0 0 1-2-2L5 6" />
|
||||
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" />
|
||||
</svg>
|
||||
<Trash2 size={16} />
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -381,21 +341,7 @@
|
||||
</Card.Description>
|
||||
</div>
|
||||
<Button variant="outline" onclick={reloadData}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
|
||||
<path d="M21 3v5h-5" />
|
||||
</svg>
|
||||
<RefreshCw class="mr-2" size={16} />
|
||||
Actualizar
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -9,12 +9,78 @@ export const load: PageServerLoad = async ({ params, cookies, fetch }) => {
|
||||
throw redirect(302, '/login');
|
||||
}
|
||||
|
||||
// Obtener el company_id de la cookie para ambas ramas (new y edit)
|
||||
const companyId = await getActiveCompanyId(cookies, fetch);
|
||||
|
||||
if (!companyId) {
|
||||
throw error(400, 'No se encontró una compañía seleccionada');
|
||||
}
|
||||
|
||||
// Cargar pedimento_codes (datos de referencia)
|
||||
const pedimentoCodesPromise = authenticatedFetch(
|
||||
'v1/public/refrence_data/pedimento-codes?page=1&page_size=100',
|
||||
{},
|
||||
cookies,
|
||||
fetch
|
||||
);
|
||||
|
||||
// Cargar customs_sections (datos de referencia)
|
||||
const customsSectionsPromise = authenticatedFetch(
|
||||
'v1/public/refrence_data/customs-sections?page=1&page_size=100',
|
||||
{},
|
||||
cookies,
|
||||
fetch
|
||||
);
|
||||
|
||||
// Cargar customs_brokers (datos de referencia)
|
||||
const customsBrokersPromise = authenticatedFetch(
|
||||
`v1/a76/customs-brokers?company_id=${companyId}`,
|
||||
{},
|
||||
cookies,
|
||||
fetch
|
||||
);
|
||||
|
||||
// Cargar clientes (para el select de client_id)
|
||||
const clientsPromise = authenticatedFetch(
|
||||
`v1/a76/clients-providers?company_id=${companyId}&type=client&page=1&page_size=1000`,
|
||||
{},
|
||||
cookies,
|
||||
fetch
|
||||
);
|
||||
|
||||
// Cargar code-pedimento-regimens (para interdependencia de campos)
|
||||
const codePedimentoRegimensPromise = authenticatedFetch(
|
||||
'v1/public/refrence_data/code-pedimento-regimens?page=1&page_size=100',
|
||||
{},
|
||||
cookies,
|
||||
fetch
|
||||
);
|
||||
|
||||
// Si el ID es "new", es una creación
|
||||
if (params.id === 'new') {
|
||||
const [pedimentoCodesResponse, customsSectionsResponse, customsBrokersResponse, clientsResponse, codePedimentoRegimensResponse] = await Promise.all([
|
||||
pedimentoCodesPromise,
|
||||
customsSectionsPromise,
|
||||
customsBrokersPromise,
|
||||
clientsPromise,
|
||||
codePedimentoRegimensPromise
|
||||
]);
|
||||
|
||||
const pedimentoCodes = pedimentoCodesResponse.ok ? await pedimentoCodesResponse.json() : { items: [] };
|
||||
const customsSections = customsSectionsResponse.ok ? await customsSectionsResponse.json() : { items: [] };
|
||||
const customsBrokers = customsBrokersResponse.ok ? await customsBrokersResponse.json() : { items: [] };
|
||||
const clients = clientsResponse.ok ? await clientsResponse.json() : { items: [] };
|
||||
const codePedimentoRegimens = codePedimentoRegimensResponse.ok ? await codePedimentoRegimensResponse.json() : { items: [] };
|
||||
|
||||
return {
|
||||
pedimento: null,
|
||||
pedimentoId: null,
|
||||
isCreate: true
|
||||
isCreate: true,
|
||||
pedimentoCodes: pedimentoCodes.items || [],
|
||||
customsSections: customsSections.items || [],
|
||||
customsBrokers: customsBrokers.items || [],
|
||||
clients: clients.items || [],
|
||||
codePedimentoRegimens: codePedimentoRegimens.items || []
|
||||
};
|
||||
}
|
||||
|
||||
@@ -24,12 +90,7 @@ export const load: PageServerLoad = async ({ params, cookies, fetch }) => {
|
||||
}
|
||||
|
||||
try {
|
||||
// Obtener el company_id de la cookie
|
||||
const companyId = await getActiveCompanyId(cookies, fetch);
|
||||
|
||||
if (!companyId) {
|
||||
throw error(400, 'No se encontró una compañía seleccionada');
|
||||
}
|
||||
|
||||
|
||||
// Cargar el pedimento desde el backend usando authenticatedFetch
|
||||
const response = await authenticatedFetch(
|
||||
@@ -51,10 +112,30 @@ export const load: PageServerLoad = async ({ params, cookies, fetch }) => {
|
||||
|
||||
const pedimento = await response.json();
|
||||
|
||||
// Cargar pedimento_codes y customs_sections
|
||||
const [pedimentoCodesResponse, customsSectionsResponse, customsBrokersResponse, clientsResponse, codePedimentoRegimensResponse] = await Promise.all([
|
||||
pedimentoCodesPromise,
|
||||
customsSectionsPromise,
|
||||
customsBrokersPromise,
|
||||
clientsPromise,
|
||||
codePedimentoRegimensPromise
|
||||
]);
|
||||
|
||||
const pedimentoCodes = pedimentoCodesResponse.ok ? await pedimentoCodesResponse.json() : { items: [] };
|
||||
const customsSections = customsSectionsResponse.ok ? await customsSectionsResponse.json() : { items: [] };
|
||||
const customsBrokers = customsBrokersResponse.ok ? await customsBrokersResponse.json() : { items: [] };
|
||||
const clients = clientsResponse.ok ? await clientsResponse.json() : { items: [] };
|
||||
const codePedimentoRegimens = codePedimentoRegimensResponse.ok ? await codePedimentoRegimensResponse.json() : { items: [] };
|
||||
|
||||
return {
|
||||
pedimento,
|
||||
pedimentoId,
|
||||
isCreate: false
|
||||
isCreate: false,
|
||||
pedimentoCodes: pedimentoCodes.items || [],
|
||||
customsSections: customsSections.items || [],
|
||||
customsBrokers: customsBrokers.items || [],
|
||||
clients: clients.items || [],
|
||||
codePedimentoRegimens: codePedimentoRegimens.items || []
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('Error loading pedimento:', e);
|
||||
|
||||
@@ -7,6 +7,18 @@
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Badge } from '$lib/components/ui/badge';
|
||||
import { Separator } from '$lib/components/ui/separator';
|
||||
import {
|
||||
ArrowLeft,
|
||||
CircleAlert,
|
||||
CircleCheck,
|
||||
FileText,
|
||||
Calendar,
|
||||
CreditCard,
|
||||
Truck,
|
||||
ShieldCheck,
|
||||
LoaderCircle,
|
||||
Save
|
||||
} from 'lucide-svelte';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
// Importar los componentes de cada pestaña (ahora sin botones de guardar propios)
|
||||
@@ -18,8 +30,27 @@
|
||||
|
||||
// Importar solo la API de pedimentos
|
||||
import { pedimentosApi, type CreatePedimentoData, type UpdatePedimentoData } from '$lib/api/dashboard/a76/pedimentos';
|
||||
import type { PedimentoCode } from '$lib/api/dashboard/refrence_data/pedimento_codes';
|
||||
import type { CustomsSection } from '$lib/api/dashboard/refrence_data/customs_sections';
|
||||
import type { CustomsBroker } from '$lib/api/dashboard/a76/customs-brokers';
|
||||
import type { ClientProvider } from '$lib/api/dashboard/a76/clients-providers';
|
||||
import type { CodePedimentoRegimen } from '$lib/api/dashboard/refrence_data/code_pedimento_regimens';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
interface ExtendedPageData {
|
||||
pedimentoId?: number | null;
|
||||
pedimento?: any;
|
||||
isCreate?: boolean;
|
||||
pedimentoCodes?: PedimentoCode[];
|
||||
customsSections?: CustomsSection[];
|
||||
customsBrokers?: CustomsBroker[];
|
||||
clients?: ClientProvider[];
|
||||
codePedimentoRegimens?: CodePedimentoRegimen[];
|
||||
user?: any;
|
||||
companies?: any[];
|
||||
authenticated?: boolean;
|
||||
}
|
||||
|
||||
let { data }: { data: ExtendedPageData } = $props();
|
||||
|
||||
let activeTab = $state('general');
|
||||
let saving = $state(false);
|
||||
@@ -27,7 +58,7 @@
|
||||
let success = $state(false);
|
||||
|
||||
// ID del pedimento
|
||||
let pedimentoId = $state<number | null>(data.pedimentoId);
|
||||
let pedimentoId = $state<number | null>(data.pedimentoId ?? null);
|
||||
|
||||
// Referencias a los componentes de formulario para obtener sus datos
|
||||
let generalFormData = $state<any>(null);
|
||||
@@ -66,6 +97,7 @@
|
||||
success = false;
|
||||
|
||||
try {
|
||||
|
||||
// Validar campos requeridos para creación
|
||||
if (data.isCreate && generalFormData) {
|
||||
const requiredFields = {
|
||||
@@ -109,28 +141,95 @@
|
||||
usd_value: generalFormData?.usd_value || undefined,
|
||||
paid_price: generalFormData?.paid_price || undefined,
|
||||
gross_weight: generalFormData?.gross_weight || undefined,
|
||||
exchange_rate: generalFormData?.exchange_rate || undefined,
|
||||
// Sub-recursos
|
||||
pedimento_dates: (datesFormData?.entry_date || datesFormData?.pedimento_date || datesFormData?.payment_date) ? {
|
||||
entry_date: datesFormData.entry_date || null,
|
||||
pedimento_date: datesFormData.pedimento_date || null,
|
||||
payment_date: datesFormData.payment_date || null
|
||||
} : undefined,
|
||||
pedimento_payments: (paymentsFormData?.payment_form || paymentsFormData?.bank_identifier) ? {
|
||||
payment_form: paymentsFormData.payment_form || null,
|
||||
bank_identifier: paymentsFormData.bank_identifier || null
|
||||
} : undefined,
|
||||
pedimento_transport_means: (transportFormData?.arrival_key || transportFormData?.arrival_data || transportFormData?.departure_key || transportFormData?.departure_data) ? {
|
||||
arrival_key: transportFormData.arrival_key || null,
|
||||
arrival_data: transportFormData.arrival_data || null,
|
||||
departure_key: transportFormData.departure_key || null,
|
||||
departure_data: transportFormData.departure_data || null
|
||||
} : undefined,
|
||||
pedimento_validation: validationFormData?.document ? {
|
||||
document: validationFormData.document || null
|
||||
} : undefined
|
||||
exchange_rate: generalFormData?.exchange_rate || undefined
|
||||
};
|
||||
|
||||
// Solo agregar sub-recursos en modo UPDATE (no en CREATE)
|
||||
// Y solo si tienen valores reales (no enviar objetos vacíos/null)
|
||||
if (!data.isCreate) {
|
||||
// Dates - solo enviar si hay al menos un campo con valor
|
||||
if (datesFormData) {
|
||||
const hasDateValue = datesFormData.entry_date || datesFormData.pedimento_date ||
|
||||
datesFormData.payment_date || datesFormData.rectification_payment_date ||
|
||||
datesFormData.extraction_date || datesFormData.submission_date ||
|
||||
datesFormData.eucan_date || datesFormData.original_date ||
|
||||
datesFormData.start_date || datesFormData.end_date;
|
||||
|
||||
if (hasDateValue) {
|
||||
payload.pedimento_dates = {
|
||||
entry_date: datesFormData.entry_date || null,
|
||||
pedimento_date: datesFormData.pedimento_date || null,
|
||||
payment_date: datesFormData.payment_date || null,
|
||||
rectification_payment_date: datesFormData.rectification_payment_date || null,
|
||||
extraction_date: datesFormData.extraction_date || null,
|
||||
submission_date: datesFormData.submission_date || null,
|
||||
eucan_date: datesFormData.eucan_date || null,
|
||||
original_date: datesFormData.original_date || null,
|
||||
start_date: datesFormData.start_date || null,
|
||||
end_date: datesFormData.end_date || null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Payments - solo enviar si hay al menos un campo con valor
|
||||
if (paymentsFormData) {
|
||||
const hasPaymentValue = paymentsFormData.acknowledgment || paymentsFormData.operation_number ||
|
||||
paymentsFormData.bank_code || paymentsFormData.cashier || paymentsFormData.date ||
|
||||
paymentsFormData.time || paymentsFormData.shift || paymentsFormData.total_cash_paid ||
|
||||
paymentsFormData.total_contributions || paymentsFormData.counter_payment ||
|
||||
paymentsFormData.pece_code;
|
||||
if (hasPaymentValue) {
|
||||
payload.pedimento_payments = {
|
||||
acknowledgment: paymentsFormData.acknowledgment || null,
|
||||
operation_number: paymentsFormData.operation_number || null,
|
||||
bank_code: paymentsFormData.bank_code || null,
|
||||
cashier: paymentsFormData.cashier || null,
|
||||
date: paymentsFormData.date || null,
|
||||
time: paymentsFormData.time || null,
|
||||
shift: paymentsFormData.shift || null,
|
||||
total_cash_paid: paymentsFormData.total_cash_paid || null,
|
||||
total_contributions: paymentsFormData.total_contributions || null,
|
||||
counter_payment: paymentsFormData.counter_payment || null,
|
||||
pece_code: paymentsFormData.pece_code || null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Transport - solo enviar si hay al menos un campo con valor
|
||||
if (transportFormData) {
|
||||
const hasTransportValue = transportFormData.destination || transportFormData.entry_exit ||
|
||||
transportFormData.arrival || transportFormData.departure;
|
||||
if (hasTransportValue) {
|
||||
payload.pedimento_transport_means = {
|
||||
destination: transportFormData.destination || null,
|
||||
entry_exit: transportFormData.entry_exit || null,
|
||||
arrival: transportFormData.arrival || null,
|
||||
departure: transportFormData.departure || null
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Validation - solo enviar si hay al menos un campo con valor
|
||||
if (validationFormData) {
|
||||
const hasValidationValue = validationFormData.validator || validationFormData.validation_ack ||
|
||||
validationFormData.pre_ack || validationFormData.line_signature ||
|
||||
validationFormData.electronic_signature || validationFormData.certificate_number ||
|
||||
validationFormData.validator_id || validationFormData.responsible_id;
|
||||
if (hasValidationValue) {
|
||||
payload.pedimento_validation = {
|
||||
validator: validationFormData.validator || null,
|
||||
validation_ack: validationFormData.validation_ack || null,
|
||||
pre_ack: validationFormData.pre_ack || null,
|
||||
line_signature: validationFormData.line_signature || null,
|
||||
electronic_signature: validationFormData.electronic_signature || null,
|
||||
certificate_number: validationFormData.certificate_number || null,
|
||||
validator_id: validationFormData.validator_id || null,
|
||||
responsible_id: validationFormData.responsible_id || null
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Eliminar campos undefined para no enviarlos
|
||||
Object.keys(payload).forEach(key => {
|
||||
if (payload[key as keyof typeof payload] === undefined) {
|
||||
@@ -140,9 +239,9 @@
|
||||
|
||||
let newPedimentoId = pedimentoId;
|
||||
|
||||
if (data.isCreate) {
|
||||
if (data.isCreate) {
|
||||
// Crear nuevo pedimento con todos sus sub-recursos
|
||||
const response = await pedimentosApi.create(payload as CreatePedimentoData);
|
||||
const response = await pedimentosApi.create(payload as CreatePedimentoData);
|
||||
if (response.error) {
|
||||
const errorMsg = typeof response.error === 'string' ? response.error : 'Error al crear el pedimento';
|
||||
throw new Error(errorMsg);
|
||||
@@ -153,12 +252,12 @@
|
||||
// Redirigir a la página de edición
|
||||
await goto(`/dashboard/pedimentos/edit/${newPedimentoId}`);
|
||||
return;
|
||||
} else {
|
||||
} else {
|
||||
// Actualizar pedimento existente con todos sus sub-recursos
|
||||
const response = await pedimentosApi.update(pedimentoId!, payload as UpdatePedimentoData);
|
||||
const response = await pedimentosApi.update(pedimentoId!, payload as UpdatePedimentoData);
|
||||
if (response.error) throw new Error(response.error);
|
||||
}
|
||||
|
||||
|
||||
success = true;
|
||||
setTimeout(() => {
|
||||
success = false;
|
||||
@@ -185,20 +284,7 @@
|
||||
<div class="space-y-1">
|
||||
<div class="flex items-center gap-3">
|
||||
<Button variant="ghost" size="icon" onclick={handleBack}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="m12 19-7-7 7-7" />
|
||||
<path d="M19 12H5" />
|
||||
</svg>
|
||||
<ArrowLeft size={20} />
|
||||
</Button>
|
||||
<h1 class="text-3xl font-bold tracking-tight">
|
||||
{#if data.isCreate}
|
||||
@@ -230,21 +316,7 @@
|
||||
<!-- Alertas globales -->
|
||||
{#if error}
|
||||
<Alert.Root variant="destructive">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<line x1="12" x2="12" y1="8" y2="12" />
|
||||
<line x1="12" x2="12.01" y1="16" y2="16" />
|
||||
</svg>
|
||||
<CircleAlert size={16} />
|
||||
<Alert.Title>Error</Alert.Title>
|
||||
<Alert.Description>{error}</Alert.Description>
|
||||
</Alert.Root>
|
||||
@@ -252,216 +324,105 @@
|
||||
|
||||
{#if success}
|
||||
<Alert.Root>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10z" />
|
||||
<path d="m9 12 2 2 4-4" />
|
||||
</svg>
|
||||
<CircleCheck size={16} />
|
||||
<Alert.Title>Éxito</Alert.Title>
|
||||
<Alert.Description>Todos los cambios se guardaron correctamente</Alert.Description>
|
||||
</Alert.Root>
|
||||
{/if}
|
||||
|
||||
<!-- Tabs Navigation -->
|
||||
<Tabs.Root bind:value={activeTab} class="space-y-4">
|
||||
<Tabs.List class="grid w-full grid-cols-5">
|
||||
<Tabs.Trigger value="general" disabled={false}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z" />
|
||||
<polyline points="14 2 14 8 20 8" />
|
||||
</svg>
|
||||
General
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger value="dates" disabled={false}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<rect width="18" height="18" x="3" y="4" rx="2" ry="2" />
|
||||
<line x1="16" x2="16" y1="2" y2="6" />
|
||||
<line x1="8" x2="8" y1="2" y2="6" />
|
||||
<line x1="3" x2="21" y1="10" y2="10" />
|
||||
</svg>
|
||||
Fechas
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger value="payments" disabled={false}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<rect width="20" height="14" x="2" y="5" rx="2" />
|
||||
<line x1="2" x2="22" y1="10" y2="10" />
|
||||
</svg>
|
||||
Pagos
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger value="transport" disabled={false}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M14 18V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v11a1 1 0 0 0 1 1h2" />
|
||||
<path d="M15 18H9" />
|
||||
<path d="M19 18h2a1 1 0 0 0 1-1v-3.65a1 1 0 0 0-.22-.624l-3.48-4.35A1 1 0 0 0 17.52 8H14" />
|
||||
<circle cx="17" cy="18" r="2" />
|
||||
<circle cx="7" cy="18" r="2" />
|
||||
</svg>
|
||||
Transporte
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger value="validation" disabled={false}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10" />
|
||||
<path d="m9 12 2 2 4-4" />
|
||||
</svg>
|
||||
Validación
|
||||
</Tabs.Trigger>
|
||||
</Tabs.List>
|
||||
<!-- Contenido de las tabs con padding inferior para el footer flotante -->
|
||||
<div class="pb-48">
|
||||
<Tabs.Root bind:value={activeTab} class="space-y-4">
|
||||
<Tabs.Content value="general">
|
||||
<GeneralTabForm
|
||||
pedimento={data.pedimento}
|
||||
bind:formData={generalFormData}
|
||||
pedimentoCodes={data.pedimentoCodes || []}
|
||||
customsSections={data.customsSections || []}
|
||||
customsBrokers={data.customsBrokers || []}
|
||||
clients={data.clients || []}
|
||||
codePedimentoRegimens={data.codePedimentoRegimens || []}
|
||||
/>
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.Content value="general">
|
||||
<GeneralTabForm
|
||||
pedimento={data.pedimento}
|
||||
bind:formData={generalFormData}
|
||||
/>
|
||||
</Tabs.Content>
|
||||
<Tabs.Content value="dates">
|
||||
<DatesTabForm
|
||||
pedimento={data.pedimento}
|
||||
bind:formData={datesFormData}
|
||||
bind:exists={datesExists}
|
||||
/>
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.Content value="dates">
|
||||
<DatesTabForm
|
||||
pedimentoId={pedimentoId}
|
||||
bind:formData={datesFormData}
|
||||
bind:exists={datesExists}
|
||||
/>
|
||||
</Tabs.Content>
|
||||
<Tabs.Content value="payments">
|
||||
<PaymentsTabForm
|
||||
pedimento={data.pedimento}
|
||||
bind:formData={paymentsFormData}
|
||||
bind:exists={paymentsExists}
|
||||
/>
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.Content value="payments">
|
||||
<PaymentsTabForm
|
||||
pedimentoId={pedimentoId}
|
||||
bind:formData={paymentsFormData}
|
||||
bind:exists={paymentsExists}
|
||||
/>
|
||||
</Tabs.Content>
|
||||
<Tabs.Content value="transport">
|
||||
<TransportTabForm
|
||||
pedimento={data.pedimento}
|
||||
bind:formData={transportFormData}
|
||||
bind:exists={transportExists}
|
||||
/>
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.Content value="transport">
|
||||
<TransportTabForm
|
||||
pedimentoId={pedimentoId}
|
||||
bind:formData={transportFormData}
|
||||
bind:exists={transportExists}
|
||||
/>
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.Content value="validation">
|
||||
<ValidationTabForm
|
||||
pedimentoId={pedimentoId}
|
||||
bind:formData={validationFormData}
|
||||
bind:exists={validationExists}
|
||||
/>
|
||||
</Tabs.Content>
|
||||
</Tabs.Root>
|
||||
|
||||
<!-- Botón de guardar global -->
|
||||
<Card.Root>
|
||||
<Card.Content class="pt-6">
|
||||
<div class="flex justify-end gap-3">
|
||||
<Button type="button" variant="outline" onclick={handleBack} disabled={saving}>
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button onclick={handleSaveAll} disabled={saving}>
|
||||
{#if saving}
|
||||
<svg
|
||||
class="mr-2 h-4 w-4 animate-spin"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle
|
||||
class="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
stroke-width="4"
|
||||
/>
|
||||
<path
|
||||
class="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
/>
|
||||
</svg>
|
||||
Guardando todos los cambios...
|
||||
{:else}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" />
|
||||
<polyline points="17 21 17 13 7 13 7 21" />
|
||||
<polyline points="7 3 7 8 15 8" />
|
||||
</svg>
|
||||
Guardar Todos los Cambios
|
||||
{/if}
|
||||
</Button>
|
||||
</div>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
<Tabs.Content value="validation">
|
||||
<ValidationTabForm
|
||||
pedimento={data.pedimento}
|
||||
bind:formData={validationFormData}
|
||||
bind:exists={validationExists}
|
||||
/>
|
||||
</Tabs.Content>
|
||||
</Tabs.Root>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer fijo en la parte inferior - Fuera del contenedor principal -->
|
||||
<div class="fixed bottom-0 inset-x-0 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/80 border-t shadow-lg z-50 group-has-[[data-sidebar]]/sidebar-wrapper:left-[var(--sidebar-width)]">
|
||||
<div class="px-4 py-4 space-y-4 max-w-[1400px] mx-auto">
|
||||
<!-- Tabs Navigation -->
|
||||
<Tabs.Root bind:value={activeTab}>
|
||||
<Tabs.List class="grid w-full grid-cols-5">
|
||||
<Tabs.Trigger value="general" disabled={false}>
|
||||
<FileText size={16} class="mr-2" />
|
||||
General
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger value="dates" disabled={false}>
|
||||
<Calendar size={16} class="mr-2" />
|
||||
Fechas
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger value="payments" disabled={false}>
|
||||
<CreditCard size={16} class="mr-2" />
|
||||
Pagos
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger value="transport" disabled={false}>
|
||||
<Truck size={16} class="mr-2" />
|
||||
Transporte
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger value="validation" disabled={false}>
|
||||
<ShieldCheck size={16} class="mr-2" />
|
||||
Validación
|
||||
</Tabs.Trigger>
|
||||
</Tabs.List>
|
||||
</Tabs.Root>
|
||||
|
||||
<!-- Botones de acción -->
|
||||
<div class="flex justify-end gap-3">
|
||||
<Button type="button" variant="outline" onclick={handleBack} disabled={saving}>
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button onclick={handleSaveAll} disabled={saving}>
|
||||
{#if saving}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
Guardando todos los cambios...
|
||||
{:else}
|
||||
<Save size={16} class="mr-2" />
|
||||
Guardar Todos los Cambios
|
||||
{/if}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import type { PageData } from './$types';
|
||||
import { browser } from '$app/environment';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
|
||||
// Los datos iniciales vienen del servidor
|
||||
let { data }: { data: PageData } = $props();
|
||||
@@ -120,21 +121,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={handleCreateClick}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M5 12h14" />
|
||||
<path d="M12 5v14" />
|
||||
</svg>
|
||||
<Plus class="mr-2" size={16} />
|
||||
Nuevo Registro
|
||||
</Button>
|
||||
</div>
|
||||
@@ -160,21 +147,7 @@
|
||||
</Card.Description>
|
||||
</div>
|
||||
<Button variant="outline" onclick={reloadData}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
|
||||
<path d="M21 3v5h-5" />
|
||||
</svg>
|
||||
<RefreshCw class="mr-2" size={16} />
|
||||
Actualizar
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import type { PageData } from './$types';
|
||||
import { browser } from '$app/environment';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
|
||||
// Los datos iniciales vienen del servidor
|
||||
let { data }: { data: PageData } = $props();
|
||||
@@ -120,21 +121,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={handleCreateClick}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M5 12h14" />
|
||||
<path d="M12 5v14" />
|
||||
</svg>
|
||||
<Plus class="mr-2" size={16} />
|
||||
Nuevo Contenedor
|
||||
</Button>
|
||||
</div>
|
||||
@@ -160,21 +147,7 @@
|
||||
</Card.Description>
|
||||
</div>
|
||||
<Button variant="outline" onclick={reloadData}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
|
||||
<path d="M21 3v5h-5" />
|
||||
</svg>
|
||||
<RefreshCw class="mr-2" size={16} />
|
||||
Actualizar
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import CreateEditDialog from '$lib/components/dashboard/reference_data/countries/create-edit-dialog.svelte';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
import type { PageData } from './$types';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
@@ -120,21 +121,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={handleCreateClick}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M5 12h14" />
|
||||
<path d="M12 5v14" />
|
||||
</svg>
|
||||
<Plus class="mr-2" size={16} />
|
||||
Nuevo País
|
||||
</Button>
|
||||
</div>
|
||||
@@ -160,21 +147,7 @@
|
||||
</Card.Description>
|
||||
</div>
|
||||
<Button variant="outline" onclick={reloadData}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
|
||||
<path d="M21 3v5h-5" />
|
||||
</svg>
|
||||
<RefreshCw class="mr-2" size={16} />
|
||||
Actualizar
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import CreateEditDialog from '$lib/components/dashboard/reference_data/currency_types/create-edit-dialog.svelte';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
import type { PageData } from './$types';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
@@ -120,22 +121,8 @@
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={handleCreateClick}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M5 12h14" />
|
||||
<path d="M12 5v14" />
|
||||
</svg>
|
||||
Nueva Moneda
|
||||
<Plus class="mr-2" size={16} />
|
||||
Nuevo Tipo de Moneda
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -160,21 +147,7 @@
|
||||
</Card.Description>
|
||||
</div>
|
||||
<Button variant="outline" onclick={reloadData}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
|
||||
<path d="M21 3v5h-5" />
|
||||
</svg>
|
||||
<RefreshCw class="mr-2" size={16} />
|
||||
Actualizar
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import type { PageData } from './$types';
|
||||
import { browser } from '$app/environment';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
|
||||
// Los datos iniciales vienen del servidor
|
||||
let { data }: { data: PageData } = $props();
|
||||
@@ -120,21 +121,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={handleCreateClick}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M5 12h14" />
|
||||
<path d="M12 5v14" />
|
||||
</svg>
|
||||
<Plus class="mr-2" size={16} />
|
||||
Nueva Sección
|
||||
</Button>
|
||||
</div>
|
||||
@@ -160,21 +147,7 @@
|
||||
</Card.Description>
|
||||
</div>
|
||||
<Button variant="outline" onclick={reloadData}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
|
||||
<path d="M21 3v5h-5" />
|
||||
</svg>
|
||||
<RefreshCw class="mr-2" size={16} />
|
||||
Actualizar
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import type { PageData } from './$types';
|
||||
import { browser } from '$app/environment';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
|
||||
// Los datos iniciales vienen del servidor
|
||||
let { data }: { data: PageData } = $props();
|
||||
@@ -120,21 +121,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={handleCreateClick}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M5 12h14" />
|
||||
<path d="M12 5v14" />
|
||||
</svg>
|
||||
<Plus class="mr-2" size={16} />
|
||||
Nuevo Recinto
|
||||
</Button>
|
||||
</div>
|
||||
@@ -160,21 +147,7 @@
|
||||
</Card.Description>
|
||||
</div>
|
||||
<Button variant="outline" onclick={reloadData}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
|
||||
<path d="M21 3v5h-5" />
|
||||
</svg>
|
||||
<RefreshCw class="mr-2" size={16} />
|
||||
Actualizar
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import CreateEditDialog from '$lib/components/dashboard/reference_data/incoterms/create-edit-dialog.svelte';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
import type { PageData } from './$types';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
@@ -120,21 +121,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={handleCreateClick}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M5 12h14" />
|
||||
<path d="M12 5v14" />
|
||||
</svg>
|
||||
<Plus class="mr-2" size={16} />
|
||||
Nuevo Incoterm
|
||||
</Button>
|
||||
</div>
|
||||
@@ -160,21 +147,7 @@
|
||||
</Card.Description>
|
||||
</div>
|
||||
<Button variant="outline" onclick={reloadData}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
|
||||
<path d="M21 3v5h-5" />
|
||||
</svg>
|
||||
<RefreshCw class="mr-2" size={16} />
|
||||
Actualizar
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import CreateEditDialog from '$lib/components/dashboard/reference_data/invoice_types/create-edit-dialog.svelte';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
import type { PageData } from './$types';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
@@ -120,21 +121,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={handleCreateClick}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M5 12h14" />
|
||||
<path d="M12 5v14" />
|
||||
</svg>
|
||||
<Plus class="mr-2" size={16} />
|
||||
Nuevo Tipo de Factura
|
||||
</Button>
|
||||
</div>
|
||||
@@ -160,21 +147,7 @@
|
||||
</Card.Description>
|
||||
</div>
|
||||
<Button variant="outline" onclick={reloadData}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
|
||||
<path d="M21 3v5h-5" />
|
||||
</svg>
|
||||
<RefreshCw class="mr-2" size={16} />
|
||||
Actualizar
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import type { PageData } from './$types';
|
||||
import { browser } from '$app/environment';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
|
||||
// Los datos iniciales vienen del servidor
|
||||
let { data }: { data: PageData } = $props();
|
||||
@@ -120,21 +121,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={handleCreateClick}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M5 12h14" />
|
||||
<path d="M12 5v14" />
|
||||
</svg>
|
||||
<Plus class="mr-2" size={16} />
|
||||
Nuevo Tipo de Material
|
||||
</Button>
|
||||
</div>
|
||||
@@ -160,21 +147,7 @@
|
||||
</Card.Description>
|
||||
</div>
|
||||
<Button variant="outline" onclick={reloadData}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
|
||||
<path d="M21 3v5h-5" />
|
||||
</svg>
|
||||
<RefreshCw class="mr-2" size={16} />
|
||||
Actualizar
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import type { PageData } from './$types';
|
||||
import { browser } from '$app/environment';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
|
||||
// Los datos iniciales vienen del servidor
|
||||
let { data }: { data: PageData } = $props();
|
||||
@@ -120,21 +121,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={handleCreateClick}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M5 12h14" />
|
||||
<path d="M12 5v14" />
|
||||
</svg>
|
||||
<Plus class="mr-2" size={16} />
|
||||
Nuevo Método de Pago
|
||||
</Button>
|
||||
</div>
|
||||
@@ -160,21 +147,7 @@
|
||||
</Card.Description>
|
||||
</div>
|
||||
<Button variant="outline" onclick={reloadData}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
|
||||
<path d="M21 3v5h-5" />
|
||||
</svg>
|
||||
<RefreshCw class="mr-2" size={16} />
|
||||
Actualizar
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import CreateEditDialog from '$lib/components/dashboard/reference_data/pedimento_codes/create-edit-dialog.svelte';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
import type { PageData } from './$types';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
@@ -120,22 +121,8 @@
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={handleCreateClick}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M5 12h14" />
|
||||
<path d="M12 5v14" />
|
||||
</svg>
|
||||
Nueva Clave de Pedimento
|
||||
<Plus class="mr-2" size={16} />
|
||||
Nuevo Código de Pedimento
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -160,21 +147,7 @@
|
||||
</Card.Description>
|
||||
</div>
|
||||
<Button variant="outline" onclick={reloadData}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
|
||||
<path d="M21 3v5h-5" />
|
||||
</svg>
|
||||
<RefreshCw class="mr-2" size={16} />
|
||||
Actualizar
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import type { PageData } from './$types';
|
||||
import { browser } from '$app/environment';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
|
||||
// Los datos iniciales vienen del servidor
|
||||
let { data }: { data: PageData } = $props();
|
||||
@@ -120,21 +121,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={handleCreateClick}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M5 12h14" />
|
||||
<path d="M12 5v14" />
|
||||
</svg>
|
||||
<Plus class="mr-2" size={16} />
|
||||
Nuevo Régimen de Pedimento
|
||||
</Button>
|
||||
</div>
|
||||
@@ -160,21 +147,7 @@
|
||||
</Card.Description>
|
||||
</div>
|
||||
<Button variant="outline" onclick={reloadData}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
|
||||
<path d="M21 3v5h-5" />
|
||||
</svg>
|
||||
<RefreshCw class="mr-2" size={16} />
|
||||
Actualizar
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import CreateEditDialog from '$lib/components/dashboard/reference_data/sectors/create-edit-dialog.svelte';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
import type { PageData } from './$types';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
@@ -120,21 +121,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={handleCreateClick}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M5 12h14" />
|
||||
<path d="M12 5v14" />
|
||||
</svg>
|
||||
<Plus class="mr-2" size={16} />
|
||||
Nuevo Sector
|
||||
</Button>
|
||||
</div>
|
||||
@@ -160,21 +147,7 @@
|
||||
</Card.Description>
|
||||
</div>
|
||||
<Button variant="outline" onclick={reloadData}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
|
||||
<path d="M21 3v5h-5" />
|
||||
</svg>
|
||||
<RefreshCw class="mr-2" size={16} />
|
||||
Actualizar
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import type { PageData } from './$types';
|
||||
import { browser } from '$app/environment';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
|
||||
// Los datos iniciales vienen del servidor
|
||||
let { data }: { data: PageData } = $props();
|
||||
@@ -120,21 +121,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={handleCreateClick}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M5 12h14" />
|
||||
<path d="M12 5v14" />
|
||||
</svg>
|
||||
<Plus class="mr-2" size={16} />
|
||||
Nuevo Estado
|
||||
</Button>
|
||||
</div>
|
||||
@@ -160,21 +147,7 @@
|
||||
</Card.Description>
|
||||
</div>
|
||||
<Button variant="outline" onclick={reloadData}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
|
||||
<path d="M21 3v5h-5" />
|
||||
</svg>
|
||||
<RefreshCw class="mr-2" size={16} />
|
||||
Actualizar
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import type { PageData } from './$types';
|
||||
import { browser } from '$app/environment';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
|
||||
// Los datos iniciales vienen del servidor
|
||||
let { data }: { data: PageData } = $props();
|
||||
@@ -120,21 +121,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={handleCreateClick}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M5 12h14" />
|
||||
<path d="M12 5v14" />
|
||||
</svg>
|
||||
<Plus class="mr-2" size={16} />
|
||||
Nuevo Modo de Transporte
|
||||
</Button>
|
||||
</div>
|
||||
@@ -160,21 +147,7 @@
|
||||
</Card.Description>
|
||||
</div>
|
||||
<Button variant="outline" onclick={reloadData}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
|
||||
<path d="M21 3v5h-5" />
|
||||
</svg>
|
||||
<RefreshCw class="mr-2" size={16} />
|
||||
Actualizar
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import CreateEditDialog from '$lib/components/dashboard/reference_data/transport_types/create-edit-dialog.svelte';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
import type { PageData } from './$types';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
@@ -120,21 +121,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={handleCreateClick}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M5 12h14" />
|
||||
<path d="M12 5v14" />
|
||||
</svg>
|
||||
<Plus class="mr-2" size={16} />
|
||||
Nuevo Tipo de Transporte
|
||||
</Button>
|
||||
</div>
|
||||
@@ -160,21 +147,7 @@
|
||||
</Card.Description>
|
||||
</div>
|
||||
<Button variant="outline" onclick={reloadData}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
|
||||
<path d="M21 3v5h-5" />
|
||||
</svg>
|
||||
<RefreshCw class="mr-2" size={16} />
|
||||
Actualizar
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import CreateEditDialog from '$lib/components/dashboard/reference_data/valuation_methods/create-edit-dialog.svelte';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
import type { PageData } from './$types';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
@@ -120,21 +121,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={handleCreateClick}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M5 12h14" />
|
||||
<path d="M12 5v14" />
|
||||
</svg>
|
||||
<Plus size={16} class="mr-2" />
|
||||
Nuevo Método de Valoración
|
||||
</Button>
|
||||
</div>
|
||||
@@ -160,21 +147,7 @@
|
||||
</Card.Description>
|
||||
</div>
|
||||
<Button variant="outline" onclick={reloadData}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
|
||||
<path d="M21 3v5h-5" />
|
||||
</svg>
|
||||
<RefreshCw size={16} class="mr-2" />
|
||||
Actualizar
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
# se actualiza con el ID real del tenant creado en PostgreSQL.
|
||||
###############################################################################
|
||||
|
||||
set -e # Salir si hay algún error
|
||||
# set -e # Comentado para permitir que el script continúe aunque algunos comandos fallen (ej: mapper ya existe)
|
||||
|
||||
# Colores para output
|
||||
RED='\033[0;31m'
|
||||
@@ -309,87 +309,84 @@ echo -e "\n${YELLOW}[5/8] Configurando mappers para tenant_id...${NC}"
|
||||
if [ -n "$BACKEND_CLIENT_ID" ]; then
|
||||
echo "Configurando mapper para Backend..."
|
||||
|
||||
# Obtener el dedicated scope del cliente backend
|
||||
BACKEND_SCOPES=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients/${BACKEND_CLIENT_ID}/optional-client-scopes" \
|
||||
# Verificar si el mapper tenant_id ya existe en el cliente
|
||||
MAPPER_TENANT_EXISTS=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients/${BACKEND_CLIENT_ID}/protocol-mappers/models" \
|
||||
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
|
||||
-H "Content-Type: application/json")
|
||||
-H "Content-Type: application/json" | grep -o "\"name\":\"tenant-id-mapper\"")
|
||||
|
||||
# Buscar el scope dedicado
|
||||
BACKEND_DEDICATED_SCOPE_ID=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/client-scopes" \
|
||||
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
|
||||
-H "Content-Type: application/json" | grep -o "\"id\":\"[^\"]*\",\"name\":\"anexo76-backend-dedicated\"" | grep -o "\"id\":\"[^\"]*" | sed 's/"id":"//')
|
||||
|
||||
if [ -n "$BACKEND_DEDICATED_SCOPE_ID" ]; then
|
||||
# Verificar si el mapper tenant_id ya existe
|
||||
MAPPER_TENANT_EXISTS=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/client-scopes/${BACKEND_DEDICATED_SCOPE_ID}/protocol-mappers/models" \
|
||||
if [ -z "$MAPPER_TENANT_EXISTS" ]; then
|
||||
# Crear mapper para tenant_id directamente en el cliente
|
||||
CREATE_MAPPER_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients/${BACKEND_CLIENT_ID}/protocol-mappers/models" \
|
||||
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
|
||||
-H "Content-Type: application/json" | grep -o "\"name\":\"tenant-id-mapper\"")
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "tenant-id-mapper",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-attribute-mapper",
|
||||
"config": {
|
||||
"user.attribute": "tenant_id",
|
||||
"claim.name": "tenant_id",
|
||||
"jsonType.label": "String",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"userinfo.token.claim": "true"
|
||||
}
|
||||
}')
|
||||
|
||||
if [ -z "$MAPPER_TENANT_EXISTS" ]; then
|
||||
# Crear mapper para tenant_id
|
||||
curl -s -X POST "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/client-scopes/${BACKEND_DEDICATED_SCOPE_ID}/protocol-mappers/models" \
|
||||
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "tenant-id-mapper",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-attribute-mapper",
|
||||
"config": {
|
||||
"user.attribute": "tenant_id",
|
||||
"claim.name": "tenant_id",
|
||||
"jsonType.label": "String",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"userinfo.token.claim": "true"
|
||||
}
|
||||
}'
|
||||
HTTP_CODE=$(echo "$CREATE_MAPPER_RESPONSE" | tail -n1)
|
||||
if [ "$HTTP_CODE" = "201" ]; then
|
||||
echo -e "${GREEN}✓ Mapper tenant_id creado para Backend${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠ Mapper tenant_id ya existe para Backend${NC}"
|
||||
echo -e "${YELLOW}⚠ Error al crear mapper para Backend (HTTP ${HTTP_CODE})${NC}"
|
||||
echo "Respuesta: $(echo "$CREATE_MAPPER_RESPONSE" | head -n -1)"
|
||||
fi
|
||||
else
|
||||
echo -e "${YELLOW}⚠ Mapper tenant_id ya existe para Backend${NC}"
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
# 4.2 Configurar mapper para Frontend
|
||||
if [ -n "$FRONTEND_CLIENT_ID" ]; then
|
||||
echo "Configurando mapper para Frontend..."
|
||||
|
||||
# Buscar el scope dedicado del frontend
|
||||
FRONTEND_DEDICATED_SCOPE_ID=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/client-scopes" \
|
||||
# Verificar si el mapper tenant_id ya existe en el cliente
|
||||
MAPPER_TENANT_EXISTS=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients/${FRONTEND_CLIENT_ID}/protocol-mappers/models" \
|
||||
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
|
||||
-H "Content-Type: application/json" | grep -o "\"id\":\"[^\"]*\",\"name\":\"anexo76-frontend-dedicated\"" | grep -o "\"id\":\"[^\"]*" | sed 's/"id":"//')
|
||||
-H "Content-Type: application/json" | grep -o "\"name\":\"tenant-id-mapper\"")
|
||||
|
||||
if [ -n "$FRONTEND_DEDICATED_SCOPE_ID" ]; then
|
||||
# Verificar si el mapper tenant_id ya existe
|
||||
MAPPER_TENANT_EXISTS=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/client-scopes/${FRONTEND_DEDICATED_SCOPE_ID}/protocol-mappers/models" \
|
||||
if [ -z "$MAPPER_TENANT_EXISTS" ]; then
|
||||
# Crear mapper para tenant_id directamente en el cliente
|
||||
CREATE_MAPPER_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients/${FRONTEND_CLIENT_ID}/protocol-mappers/models" \
|
||||
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
|
||||
-H "Content-Type: application/json" | grep -o "\"name\":\"tenant-id-mapper\"")
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "tenant-id-mapper",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-attribute-mapper",
|
||||
"config": {
|
||||
"user.attribute": "tenant_id",
|
||||
"claim.name": "tenant_id",
|
||||
"jsonType.label": "String",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"userinfo.token.claim": "true"
|
||||
}
|
||||
}')
|
||||
|
||||
if [ -z "$MAPPER_TENANT_EXISTS" ]; then
|
||||
# Crear mapper para tenant_id
|
||||
curl -s -X POST "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/client-scopes/${FRONTEND_DEDICATED_SCOPE_ID}/protocol-mappers/models" \
|
||||
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "tenant-id-mapper",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-attribute-mapper",
|
||||
"config": {
|
||||
"user.attribute": "tenant_id",
|
||||
"claim.name": "tenant_id",
|
||||
"jsonType.label": "String",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"userinfo.token.claim": "true"
|
||||
}
|
||||
}'
|
||||
HTTP_CODE=$(echo "$CREATE_MAPPER_RESPONSE" | tail -n1)
|
||||
if [ "$HTTP_CODE" = "201" ]; then
|
||||
echo -e "${GREEN}✓ Mapper tenant_id creado para Frontend${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠ Mapper tenant_id ya existe para Frontend${NC}"
|
||||
echo -e "${YELLOW}⚠ Error al crear mapper para Frontend (HTTP ${HTTP_CODE})${NC}"
|
||||
echo "Respuesta: $(echo "$CREATE_MAPPER_RESPONSE" | head -n -1)"
|
||||
fi
|
||||
else
|
||||
echo -e "${YELLOW}⚠ Mapper tenant_id ya existe para Frontend${NC}"
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
###############################################################################
|
||||
# 6. Crear usuario demo en Keycloak
|
||||
###############################################################################
|
||||
|
||||
Reference in New Issue
Block a user