Merge pull request 'feauture-pedimentos' (#10) from feauture-pedimentos into development
Reviewed-on: ADUANASOFT/anexo76#10
This commit is contained in:
29
README.md
29
README.md
@@ -1,12 +1,13 @@
|
||||
# Anexo76
|
||||
|
||||
**Aplicación SaaS para gestión de comercio exterior conforme a Anexos 24, 31 y 22 del SAT**
|
||||
**Aplicación SaaS para gestión de comercio exterior conforme a Anexos 24, 30 y 22 del SAT**
|
||||
|
||||
Anexo76 es una plataforma multi-tenant diseñada para maquilas, empresas IMMEX y agentes aduanales, que permite gestionar inventarios, pedimentos y facturas de importación/exportación con control de licencias y cumplimiento normativo.
|
||||
|
||||
## 🏗️ Arquitectura
|
||||
|
||||
### Backend
|
||||
|
||||
- **Framework**: FastAPI 0.110+
|
||||
- **Autenticación**: Keycloak (OpenID Connect)
|
||||
- **Base de Datos**: PostgreSQL con SQLAlchemy
|
||||
@@ -20,11 +21,13 @@ Anexo76 es una plataforma multi-tenant diseñada para maquilas, empresas IMMEX y
|
||||
- `routes.py`: Endpoints API
|
||||
|
||||
### Frontend
|
||||
|
||||
- **Framework**: SvelteKit
|
||||
- **Autenticación**: keycloak-js
|
||||
- **UI**: Dashboard moderno y responsivo
|
||||
|
||||
### Infraestructura
|
||||
|
||||
- **Containerización**: Docker / Docker Compose
|
||||
- **Orquestación**: Kubernetes (futuro)
|
||||
- **Monitoreo**: Prometheus + Grafana
|
||||
@@ -64,6 +67,7 @@ anexo76/
|
||||
## 🚀 Inicio Rápido
|
||||
|
||||
### Requisitos Previos
|
||||
|
||||
- Docker y Docker Compose
|
||||
- Python 3.11+ (para desarrollo local)
|
||||
- Node.js 18+ (para desarrollo frontend)
|
||||
@@ -89,6 +93,7 @@ docker-compose up -d
|
||||
```
|
||||
|
||||
Esto iniciará:
|
||||
|
||||
- **PostgreSQL** en `localhost:5432`
|
||||
- **Keycloak** en `localhost:8080`
|
||||
- **Backend API** en `localhost:8000`
|
||||
@@ -150,17 +155,20 @@ npm run dev
|
||||
## 📦 Módulos Principales
|
||||
|
||||
### 1. **Auth** (`/v1/auth`)
|
||||
|
||||
- Login con Keycloak
|
||||
- Refresh token
|
||||
- Logout
|
||||
- Información de usuario
|
||||
|
||||
### 2. **Tenants** (`/v1/tenants`)
|
||||
|
||||
- Creación y gestión de tenants
|
||||
- Upgrade de BD compartida a dedicada
|
||||
- Gestión de realms de Keycloak
|
||||
|
||||
### 3. **Licenses** (`/v1/licenses`)
|
||||
|
||||
- Control de planes (Free, Basic, Professional, Enterprise)
|
||||
- Validación de licencias activas
|
||||
- Tracking de uso (usuarios, storage, operaciones)
|
||||
@@ -188,11 +196,13 @@ npm run dev
|
||||
### Modelo Híbrido
|
||||
|
||||
**BD Compartida** (tenants pequeños/medianos):
|
||||
|
||||
- Tabla única con `tenant_id` como foreign key
|
||||
- Row-level security
|
||||
- Más económico para clientes con bajo volumen
|
||||
|
||||
**BD Dedicada** (tenants enterprise):
|
||||
|
||||
- Base de datos PostgreSQL independiente
|
||||
- Máximo aislamiento y performance
|
||||
- Configuración almacenada en `tenants.db_config`
|
||||
@@ -219,16 +229,18 @@ service.upgrade_to_dedicated(tenant_id=123, db_config=db_config)
|
||||
|
||||
### Planes Disponibles
|
||||
|
||||
| Plan | Usuarios | Storage | Operaciones/mes | Features |
|
||||
|------|----------|---------|-----------------|----------|
|
||||
| Free | 5 | 10 GB | 1,000 | API básica |
|
||||
| Basic | 20 | 50 GB | 10,000 | + Reportes |
|
||||
| Professional | 100 | 200 GB | 50,000 | + Integraciones |
|
||||
| Enterprise | Ilimitado | Ilimitado | Ilimitado | + Soporte dedicado + BD dedicada |
|
||||
|
||||
| Plan | Usuarios | Storage | Operaciones/mes | Features |
|
||||
| ------------ | --------- | --------- | --------------- | -------------------------------- |
|
||||
| Free | 5 | 10 GB | 1,000 | API básica |
|
||||
| Basic | 20 | 50 GB | 10,000 | + Reportes |
|
||||
| Professional | 100 | 200 GB | 50,000 | + Integraciones |
|
||||
| Enterprise | Ilimitado | Ilimitado | Ilimitado | + Soporte dedicado + BD dedicada |
|
||||
|
||||
### Middleware de Validación
|
||||
|
||||
El `LicenseValidationMiddleware` verifica en cada request:
|
||||
|
||||
- ✅ Licencia activa
|
||||
- ✅ No expirada
|
||||
- ✅ Límites no excedidos
|
||||
@@ -254,6 +266,7 @@ npm test
|
||||
### Prometheus Metrics
|
||||
|
||||
El backend expone métricas en `/metrics`:
|
||||
|
||||
- Request duration
|
||||
- Request count por endpoint
|
||||
- Error rate
|
||||
@@ -262,6 +275,7 @@ El backend expone métricas en `/metrics`:
|
||||
### Logging
|
||||
|
||||
Logs estructurados con nivel configurable:
|
||||
|
||||
- INFO: Operaciones normales
|
||||
- WARNING: Validaciones fallidas
|
||||
- ERROR: Errores de sistema
|
||||
@@ -282,6 +296,7 @@ Este proyecto es privado y propietario.
|
||||
## 📞 Soporte
|
||||
|
||||
Para soporte técnico o consultas:
|
||||
|
||||
- Email: soporte@anexo76.com
|
||||
- Documentación: https://docs.anexo76.com
|
||||
|
||||
|
||||
@@ -1,424 +0,0 @@
|
||||
"""Pedimentos
|
||||
|
||||
Revision ID: 03b786378f94
|
||||
Revises: 7937209f9718
|
||||
Create Date: 2025-11-06 17:07:16.536298
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '03b786378f94'
|
||||
down_revision: Union[str, Sequence[str], None] = '54f2046774d0'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('pedimentos',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('tenant_id', sa.Integer(), nullable=False),
|
||||
sa.Column('year', sa.String(length=2), nullable=True),
|
||||
sa.Column('customs_office', sa.String(length=2), nullable=True),
|
||||
sa.Column('license', sa.String(length=4), nullable=True),
|
||||
sa.Column('pedimento_number', sa.String(length=7), nullable=True),
|
||||
sa.Column('client_id', sa.Integer(), nullable=True),
|
||||
sa.Column('operation_type', sa.Integer(), nullable=True),
|
||||
sa.Column('pedimento_type', sa.Integer(), nullable=True),
|
||||
sa.Column('pedimento_key', sa.String(length=2), nullable=True),
|
||||
sa.Column('regime', sa.String(length=3), nullable=True),
|
||||
sa.Column('status', sa.String(length=30), nullable=True),
|
||||
sa.Column('usd_value', sa.Numeric(precision=17, scale=6), nullable=True),
|
||||
sa.Column('paid_price', sa.Numeric(precision=17, scale=6), nullable=True),
|
||||
sa.Column('gross_weight', sa.Numeric(precision=19, scale=3), nullable=True),
|
||||
sa.Column('exchange_rate', sa.Numeric(precision=9, scale=5), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True),
|
||||
sa.ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], ),
|
||||
sa.PrimaryKeyConstraint('id', name='pedimentos_pkey'),
|
||||
schema='a76'
|
||||
)
|
||||
op.create_index('idx_pedimentos_client_id', 'pedimentos', ['client_id'], unique=False, schema='a76')
|
||||
op.create_index('idx_pedimentos_created_at', 'pedimentos', ['created_at'], unique=False, schema='a76')
|
||||
op.create_index('idx_pedimentos_status', 'pedimentos', ['status'], unique=False, schema='a76')
|
||||
op.create_index(op.f('ix_a76_pedimentos_tenant_id'), 'pedimentos', ['tenant_id'], unique=False, schema='a76')
|
||||
op.create_table('pedimento_config_additional',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('pedimento_id', sa.Integer(), nullable=False),
|
||||
sa.Column('tenant_id', sa.Integer(), nullable=False),
|
||||
sa.Column('add_po_identifier', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('do_not_exempt_norms_complement_x', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('manual_pedimento_year', sa.String(length=2), nullable=True),
|
||||
sa.Column('enable_import_invoice_recipient', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('send_502_validation_file_for_consolidated', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('add_remove_norms', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True),
|
||||
sa.ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], name='fk_pedimento_config_additional', ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], ),
|
||||
sa.PrimaryKeyConstraint('id', name='pedimento_config_additional_pkey'),
|
||||
sa.UniqueConstraint('pedimento_id', name='pedimento_config_additional_pedimento_id_key'),
|
||||
schema='a76'
|
||||
)
|
||||
op.create_index(op.f('ix_a76_pedimento_config_additional_tenant_id'), 'pedimento_config_additional', ['tenant_id'], unique=False, schema='a76')
|
||||
op.create_table('pedimento_config_calculations',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('pedimento_id', sa.Integer(), nullable=False),
|
||||
sa.Column('tenant_id', sa.Integer(), nullable=False),
|
||||
sa.Column('dta_type', sa.String(length=1), nullable=True),
|
||||
sa.Column('dta_operation', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('dta_vehicle_count', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('dta_mixed_rate_8permil', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('pays_vat', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('pays_prevalidation', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('include_sagar_certificate_fee', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('fixed_vehicle_dta_fee', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('additional_fixed_fee', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('additional_fixed_fee_payment_method', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True),
|
||||
sa.ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], name='fk_pedimento_config_calculations', ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], ),
|
||||
sa.PrimaryKeyConstraint('id', name='pedimento_config_calculations_pkey'),
|
||||
sa.UniqueConstraint('pedimento_id', name='pedimento_config_calculations_pedimento_id_key'),
|
||||
schema='a76'
|
||||
)
|
||||
op.create_index(op.f('ix_a76_pedimento_config_calculations_tenant_id'), 'pedimento_config_calculations', ['tenant_id'], unique=False, schema='a76')
|
||||
op.create_table('pedimento_config_parameters',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('pedimento_id', sa.Integer(), nullable=False),
|
||||
sa.Column('tenant_id', sa.Integer(), nullable=False),
|
||||
sa.Column('is_embassy', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('embassy_dta', sa.Numeric(precision=11, scale=2), nullable=True),
|
||||
sa.Column('rule_3121_section_ii', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('use_previous_tariff', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('use_payment_date_fi', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('add_state_supplier_record_505', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('customs_value_calculation', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('two_decimals_unit_value', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('customs_value_per_item', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('is_national_supplier', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('is_consolidated', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True),
|
||||
sa.ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], name='fk_pedimento_config_parameters', ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], ),
|
||||
sa.PrimaryKeyConstraint('id', name='pedimento_config_parameters_pkey'),
|
||||
sa.UniqueConstraint('pedimento_id', name='pedimento_config_parameters_pedimento_id_key'),
|
||||
schema='a76'
|
||||
)
|
||||
op.create_index(op.f('ix_a76_pedimento_config_parameters_tenant_id'), 'pedimento_config_parameters', ['tenant_id'], unique=False, schema='a76')
|
||||
op.create_table('pedimento_config_surcharges',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('pedimento_id', sa.Integer(), nullable=False),
|
||||
sa.Column('tenant_id', sa.Integer(), nullable=False),
|
||||
sa.Column('surcharge_igi', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('surcharge_dta', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('surcharge_vat', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('surcharge_isan', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('surcharge_ieps', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('surcharge_cc', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True),
|
||||
sa.ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], name='fk_pedimento_config_surcharges', ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], ),
|
||||
sa.PrimaryKeyConstraint('id', name='pedimento_config_surcharges_pkey'),
|
||||
sa.UniqueConstraint('pedimento_id', name='pedimento_config_surcharges_pedimento_id_key'),
|
||||
schema='a76'
|
||||
)
|
||||
op.create_index(op.f('ix_a76_pedimento_config_surcharges_tenant_id'), 'pedimento_config_surcharges', ['tenant_id'], unique=False, schema='a76')
|
||||
op.create_table('pedimento_config_update_rectification',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('pedimento_id', sa.Integer(), nullable=False),
|
||||
sa.Column('tenant_id', sa.Integer(), nullable=False),
|
||||
sa.Column('update_vat', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('update_advalorem', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('update_cc', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('update_ieps', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('calculate_surcharge', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True),
|
||||
sa.ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], name='fk_pedimento_config_update_rectification', ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], ),
|
||||
sa.PrimaryKeyConstraint('id', name='pedimento_config_update_rectification_pkey'),
|
||||
sa.UniqueConstraint('pedimento_id', name='pedimento_config_update_rectification_pedimento_id_key'),
|
||||
schema='a76'
|
||||
)
|
||||
op.create_index(op.f('ix_a76_pedimento_config_update_rectification_tenant_id'), 'pedimento_config_update_rectification', ['tenant_id'], unique=False, schema='a76')
|
||||
op.create_table('pedimento_config_updates',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('pedimento_id', sa.Integer(), nullable=False),
|
||||
sa.Column('tenant_id', sa.Integer(), nullable=False),
|
||||
sa.Column('update_vat', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('update_advalorem', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('update_cc', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('update_ieps', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True),
|
||||
sa.ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], name='fk_pedimento_config_updates', ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], ),
|
||||
sa.PrimaryKeyConstraint('id', name='pedimento_config_updates_pkey'),
|
||||
sa.UniqueConstraint('pedimento_id', name='pedimento_config_updates_pedimento_id_key'),
|
||||
schema='a76'
|
||||
)
|
||||
op.create_index(op.f('ix_a76_pedimento_config_updates_tenant_id'), 'pedimento_config_updates', ['tenant_id'], unique=False, schema='a76')
|
||||
op.create_table('pedimento_customs_offices',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('pedimento_id', sa.Integer(), nullable=False),
|
||||
sa.Column('tenant_id', sa.Integer(), nullable=False),
|
||||
sa.Column('dispatch_customs', sa.String(length=3), nullable=True),
|
||||
sa.Column('entry_exit_customs', sa.String(length=3), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True),
|
||||
sa.ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], name='fk_pedimento_customs_offices', ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], ),
|
||||
sa.PrimaryKeyConstraint('id', name='pedimento_customs_offices_pkey'),
|
||||
sa.UniqueConstraint('pedimento_id', name='pedimento_customs_offices_pedimento_id_key'),
|
||||
schema='a76'
|
||||
)
|
||||
op.create_index(op.f('ix_a76_pedimento_customs_offices_tenant_id'), 'pedimento_customs_offices', ['tenant_id'], unique=False, schema='a76')
|
||||
op.create_table('pedimento_dates',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('pedimento_id', sa.Integer(), nullable=False),
|
||||
sa.Column('tenant_id', sa.Integer(), nullable=False),
|
||||
sa.Column('entry_date', sa.DateTime(), nullable=True),
|
||||
sa.Column('pedimento_date', sa.DateTime(), nullable=True),
|
||||
sa.Column('payment_date', sa.DateTime(), nullable=True),
|
||||
sa.Column('rectification_payment_date', sa.DateTime(), nullable=True),
|
||||
sa.Column('extraction_date', sa.DateTime(), nullable=True),
|
||||
sa.Column('submission_date', sa.DateTime(), nullable=True),
|
||||
sa.Column('eucan_date', sa.DateTime(), nullable=True),
|
||||
sa.Column('original_date', sa.DateTime(), nullable=True),
|
||||
sa.Column('start_date', sa.DateTime(), nullable=True),
|
||||
sa.Column('end_date', sa.DateTime(), nullable=True),
|
||||
sa.Column('capture_date', sa.DateTime(), nullable=True),
|
||||
sa.Column('capture_time', sa.Time(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True),
|
||||
sa.ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], name='fk_pedimento_dates', ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], ),
|
||||
sa.PrimaryKeyConstraint('id', name='pedimento_dates_pkey'),
|
||||
sa.UniqueConstraint('pedimento_id', name='pedimento_dates_pedimento_id_key'),
|
||||
schema='a76'
|
||||
)
|
||||
op.create_index('idx_pedimento_dates_pedimento_id', 'pedimento_dates', ['pedimento_id'], unique=False, schema='a76')
|
||||
op.create_index(op.f('ix_a76_pedimento_dates_tenant_id'), 'pedimento_dates', ['tenant_id'], unique=False, schema='a76')
|
||||
op.create_table('pedimento_decrementables',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('pedimento_id', sa.Integer(), nullable=False),
|
||||
sa.Column('tenant_id', sa.Integer(), nullable=False),
|
||||
sa.Column('freight', sa.Numeric(precision=13, scale=2), nullable=True),
|
||||
sa.Column('insurance', sa.Numeric(precision=13, scale=2), nullable=True),
|
||||
sa.Column('loading', sa.Numeric(precision=13, scale=2), nullable=True),
|
||||
sa.Column('unloading', sa.Numeric(precision=13, scale=2), nullable=True),
|
||||
sa.Column('others', sa.Numeric(precision=13, scale=2), nullable=True),
|
||||
sa.Column('currency', sa.String(length=3), nullable=True),
|
||||
sa.Column('currency_factor', sa.Numeric(precision=15, scale=8), nullable=True),
|
||||
sa.Column('not_affect_usd_value', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('not_affect_customs_value', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True),
|
||||
sa.ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], name='fk_pedimento_decrementables', ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], ),
|
||||
sa.PrimaryKeyConstraint('id', name='pedimento_decrementables_pkey'),
|
||||
sa.UniqueConstraint('pedimento_id', name='pedimento_decrementables_pedimento_id_key'),
|
||||
schema='a76'
|
||||
)
|
||||
op.create_index(op.f('ix_a76_pedimento_decrementables_tenant_id'), 'pedimento_decrementables', ['tenant_id'], unique=False, schema='a76')
|
||||
op.create_table('pedimento_incrementables',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('pedimento_id', sa.Integer(), nullable=False),
|
||||
sa.Column('tenant_id', sa.Integer(), nullable=False),
|
||||
sa.Column('insured_value', sa.Numeric(precision=13, scale=2), nullable=True),
|
||||
sa.Column('freight', sa.Numeric(precision=13, scale=2), nullable=True),
|
||||
sa.Column('insurance', sa.Numeric(precision=13, scale=2), nullable=True),
|
||||
sa.Column('packaging', sa.Numeric(precision=13, scale=2), nullable=True),
|
||||
sa.Column('others', sa.Numeric(precision=13, scale=3), nullable=True),
|
||||
sa.Column('deductibles', sa.Numeric(precision=13, scale=3), nullable=True),
|
||||
sa.Column('currency', sa.String(length=3), nullable=True),
|
||||
sa.Column('currency_factor', sa.Numeric(precision=15, scale=8), nullable=True),
|
||||
sa.Column('not_affect_usd_value', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('not_affect_customs_value', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True),
|
||||
sa.ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], name='fk_pedimento_incrementables', ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], ),
|
||||
sa.PrimaryKeyConstraint('id', name='pedimento_incrementables_pkey'),
|
||||
sa.UniqueConstraint('pedimento_id', name='pedimento_incrementables_pedimento_id_key'),
|
||||
schema='a76'
|
||||
)
|
||||
op.create_index(op.f('ix_a76_pedimento_incrementables_tenant_id'), 'pedimento_incrementables', ['tenant_id'], unique=False, schema='a76')
|
||||
op.create_table('pedimento_indexes',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('pedimento_id', sa.Integer(), nullable=False),
|
||||
sa.Column('tenant_id', sa.Integer(), nullable=False),
|
||||
sa.Column('update_factor_type', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('update_factor', sa.Numeric(precision=7, scale=4), nullable=True),
|
||||
sa.Column('manual_update_factor', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True),
|
||||
sa.ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], name='fk_pedimento_indexes', ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], ),
|
||||
sa.PrimaryKeyConstraint('id', name='pedimento_indexes_pkey'),
|
||||
sa.UniqueConstraint('pedimento_id', name='pedimento_indexes_pedimento_id_key'),
|
||||
schema='a76'
|
||||
)
|
||||
op.create_index(op.f('ix_a76_pedimento_indexes_tenant_id'), 'pedimento_indexes', ['tenant_id'], unique=False, schema='a76')
|
||||
op.create_table('pedimento_payments',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('pedimento_id', sa.Integer(), nullable=False),
|
||||
sa.Column('tenant_id', sa.Integer(), nullable=False),
|
||||
sa.Column('acknowledgment', sa.String(length=20), nullable=True),
|
||||
sa.Column('operation_number', sa.String(length=14), nullable=True),
|
||||
sa.Column('bank_code', sa.Integer(), nullable=True),
|
||||
sa.Column('cashier', sa.String(length=2), nullable=True),
|
||||
sa.Column('date', sa.Date(), nullable=True),
|
||||
sa.Column('time', sa.Time(), nullable=True),
|
||||
sa.Column('shift', sa.String(length=1), nullable=True),
|
||||
sa.Column('total_cash_paid', sa.Integer(), nullable=True),
|
||||
sa.Column('total_contributions', sa.Integer(), nullable=True),
|
||||
sa.Column('counter_payment', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('pece_code', sa.String(length=5), nullable=True),
|
||||
sa.Column('payment_id', sa.Integer(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True),
|
||||
sa.ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], name='fk_pedimento_payments', ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], ),
|
||||
sa.PrimaryKeyConstraint('id', name='pedimento_payments_pkey'),
|
||||
sa.UniqueConstraint('pedimento_id', name='pedimento_payments_pedimento_id_key'),
|
||||
schema='a76'
|
||||
)
|
||||
op.create_index('idx_pedimento_payments_pedimento_id', 'pedimento_payments', ['pedimento_id'], unique=False, schema='a76')
|
||||
op.create_index(op.f('ix_a76_pedimento_payments_tenant_id'), 'pedimento_payments', ['tenant_id'], unique=False, schema='a76')
|
||||
op.create_table('pedimento_rectification_destination',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('pedimento_id', sa.Integer(), nullable=False),
|
||||
sa.Column('tenant_id', sa.Integer(), nullable=False),
|
||||
sa.Column('destination_pedimento_year', sa.String(length=2), nullable=True),
|
||||
sa.Column('destination_customs_office', sa.String(length=3), nullable=True),
|
||||
sa.Column('destination_license', sa.String(length=4), nullable=True),
|
||||
sa.Column('destination_pedimento_number', sa.String(length=7), nullable=True),
|
||||
sa.ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], name='fk_pedimento_rectification_destination', ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], ),
|
||||
sa.PrimaryKeyConstraint('id', name='pedimento_rectification_destination_pkey'),
|
||||
sa.UniqueConstraint('pedimento_id', name='pedimento_rectification_destination_pedimento_id_key'),
|
||||
schema='a76'
|
||||
)
|
||||
op.create_index(op.f('ix_a76_pedimento_rectification_destination_tenant_id'), 'pedimento_rectification_destination', ['tenant_id'], unique=False, schema='a76')
|
||||
op.create_table('pedimento_rectification_origin',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('pedimento_id', sa.Integer(), nullable=False),
|
||||
sa.Column('tenant_id', sa.Integer(), nullable=False),
|
||||
sa.Column('original_pedimento_year', sa.String(length=2), nullable=True),
|
||||
sa.Column('original_customs_office', sa.String(length=3), nullable=True),
|
||||
sa.Column('original_license', sa.String(length=4), nullable=True),
|
||||
sa.Column('original_pedimento_number', sa.String(length=7), nullable=True),
|
||||
sa.Column('original_pedimento_key', sa.String(length=2), nullable=True),
|
||||
sa.Column('original_payment_date', sa.DateTime(), nullable=True),
|
||||
sa.Column('total_cash', sa.Integer(), nullable=True),
|
||||
sa.Column('total_others', sa.Integer(), nullable=True),
|
||||
sa.Column('reason', sa.String(length=255), nullable=True),
|
||||
sa.Column('charge_to_client', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('use_original_payment_date_for_interest_calc', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('manual_calculation', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('original_pedimento_norms', sa.SmallInteger(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], name='fk_pedimento_rectification_origin', ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], ),
|
||||
sa.PrimaryKeyConstraint('id', name='pedimento_rectification_origin_pkey'),
|
||||
sa.UniqueConstraint('pedimento_id', name='pedimento_rectification_origin_pedimento_id_key'),
|
||||
schema='a76'
|
||||
)
|
||||
op.create_index(op.f('ix_a76_pedimento_rectification_origin_tenant_id'), 'pedimento_rectification_origin', ['tenant_id'], unique=False, schema='a76')
|
||||
op.create_table('pedimento_transport_means',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('pedimento_id', sa.Integer(), nullable=False),
|
||||
sa.Column('tenant_id', sa.Integer(), nullable=False),
|
||||
sa.Column('destination', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('entry_exit', sa.String(length=2), nullable=True),
|
||||
sa.Column('arrival', sa.String(length=2), nullable=True),
|
||||
sa.Column('departure', sa.String(length=2), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True),
|
||||
sa.ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], name='fk_pedimento_transport_means', ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], ),
|
||||
sa.PrimaryKeyConstraint('id', name='pedimento_transport_means_pkey'),
|
||||
sa.UniqueConstraint('pedimento_id', name='pedimento_transport_means_pedimento_id_key'),
|
||||
schema='a76'
|
||||
)
|
||||
op.create_index(op.f('ix_a76_pedimento_transport_means_tenant_id'), 'pedimento_transport_means', ['tenant_id'], unique=False, schema='a76')
|
||||
op.create_table('pedimento_validation',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('pedimento_id', sa.Integer(), nullable=False),
|
||||
sa.Column('tenant_id', sa.Integer(), nullable=False),
|
||||
sa.Column('validator', sa.String(length=3), nullable=True),
|
||||
sa.Column('validation_ack', sa.String(length=8), nullable=True),
|
||||
sa.Column('pre_ack', sa.String(length=8), nullable=True),
|
||||
sa.Column('line_signature', sa.String(length=50), nullable=True),
|
||||
sa.Column('electronic_signature', sa.String(length=999), nullable=True),
|
||||
sa.Column('certificate_number', sa.String(length=99), nullable=True),
|
||||
sa.Column('validator_id', sa.Integer(), nullable=True),
|
||||
sa.Column('responsible_id', sa.Integer(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True),
|
||||
sa.ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], name='fk_pedimento_validation', ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], ),
|
||||
sa.PrimaryKeyConstraint('id', name='pedimento_validation_pkey'),
|
||||
sa.UniqueConstraint('pedimento_id', name='pedimento_validation_pedimento_id_key'),
|
||||
schema='a76'
|
||||
)
|
||||
op.create_index(op.f('ix_a76_pedimento_validation_tenant_id'), 'pedimento_validation', ['tenant_id'], unique=False, schema='a76')
|
||||
op.drop_constraint(op.f('fk_regimenped'), 'code_pedimento_regimens', type_='foreignkey')
|
||||
op.drop_constraint(op.f('fk_codeped'), 'code_pedimento_regimens', type_='foreignkey')
|
||||
op.create_foreign_key('fk_codeped', 'code_pedimento_regimens', 'pedimento_codes', ['pedimento_code'], ['code'], source_schema='public', referent_schema='public')
|
||||
op.create_foreign_key('fk_regimenped', 'code_pedimento_regimens', 'pedimento_regimens', ['regimen_code'], ['code'], source_schema='public', referent_schema='public')
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_constraint('fk_regimenped', 'code_pedimento_regimens', schema='public', type_='foreignkey')
|
||||
op.drop_constraint('fk_codeped', 'code_pedimento_regimens', schema='public', type_='foreignkey')
|
||||
op.create_foreign_key(op.f('fk_codeped'), 'code_pedimento_regimens', 'pedimento_codes', ['pedimento_code'], ['code'])
|
||||
op.create_foreign_key(op.f('fk_regimenped'), 'code_pedimento_regimens', 'pedimento_regimens', ['regimen_code'], ['code'])
|
||||
op.drop_index(op.f('ix_a76_pedimento_validation_tenant_id'), table_name='pedimento_validation', schema='a76')
|
||||
op.drop_table('pedimento_validation', schema='a76')
|
||||
op.drop_index(op.f('ix_a76_pedimento_transport_means_tenant_id'), table_name='pedimento_transport_means', schema='a76')
|
||||
op.drop_table('pedimento_transport_means', schema='a76')
|
||||
op.drop_index(op.f('ix_a76_pedimento_rectification_origin_tenant_id'), table_name='pedimento_rectification_origin', schema='a76')
|
||||
op.drop_table('pedimento_rectification_origin', schema='a76')
|
||||
op.drop_index(op.f('ix_a76_pedimento_rectification_destination_tenant_id'), table_name='pedimento_rectification_destination', schema='a76')
|
||||
op.drop_table('pedimento_rectification_destination', schema='a76')
|
||||
op.drop_index(op.f('ix_a76_pedimento_payments_tenant_id'), table_name='pedimento_payments', schema='a76')
|
||||
op.drop_index('idx_pedimento_payments_pedimento_id', table_name='pedimento_payments', schema='a76')
|
||||
op.drop_table('pedimento_payments', schema='a76')
|
||||
op.drop_index(op.f('ix_a76_pedimento_indexes_tenant_id'), table_name='pedimento_indexes', schema='a76')
|
||||
op.drop_table('pedimento_indexes', schema='a76')
|
||||
op.drop_index(op.f('ix_a76_pedimento_incrementables_tenant_id'), table_name='pedimento_incrementables', schema='a76')
|
||||
op.drop_table('pedimento_incrementables', schema='a76')
|
||||
op.drop_index(op.f('ix_a76_pedimento_decrementables_tenant_id'), table_name='pedimento_decrementables', schema='a76')
|
||||
op.drop_table('pedimento_decrementables', schema='a76')
|
||||
op.drop_index(op.f('ix_a76_pedimento_dates_tenant_id'), table_name='pedimento_dates', schema='a76')
|
||||
op.drop_index('idx_pedimento_dates_pedimento_id', table_name='pedimento_dates', schema='a76')
|
||||
op.drop_table('pedimento_dates', schema='a76')
|
||||
op.drop_index(op.f('ix_a76_pedimento_customs_offices_tenant_id'), table_name='pedimento_customs_offices', schema='a76')
|
||||
op.drop_table('pedimento_customs_offices', schema='a76')
|
||||
op.drop_index(op.f('ix_a76_pedimento_config_updates_tenant_id'), table_name='pedimento_config_updates', schema='a76')
|
||||
op.drop_table('pedimento_config_updates', schema='a76')
|
||||
op.drop_index(op.f('ix_a76_pedimento_config_update_rectification_tenant_id'), table_name='pedimento_config_update_rectification', schema='a76')
|
||||
op.drop_table('pedimento_config_update_rectification', schema='a76')
|
||||
op.drop_index(op.f('ix_a76_pedimento_config_surcharges_tenant_id'), table_name='pedimento_config_surcharges', schema='a76')
|
||||
op.drop_table('pedimento_config_surcharges', schema='a76')
|
||||
op.drop_index(op.f('ix_a76_pedimento_config_parameters_tenant_id'), table_name='pedimento_config_parameters', schema='a76')
|
||||
op.drop_table('pedimento_config_parameters', schema='a76')
|
||||
op.drop_index(op.f('ix_a76_pedimento_config_calculations_tenant_id'), table_name='pedimento_config_calculations', schema='a76')
|
||||
op.drop_table('pedimento_config_calculations', schema='a76')
|
||||
op.drop_index(op.f('ix_a76_pedimento_config_additional_tenant_id'), table_name='pedimento_config_additional', schema='a76')
|
||||
op.drop_table('pedimento_config_additional', schema='a76')
|
||||
op.drop_index(op.f('ix_a76_pedimentos_tenant_id'), table_name='pedimentos', schema='a76')
|
||||
op.drop_index('idx_pedimentos_status', table_name='pedimentos', schema='a76')
|
||||
op.drop_index('idx_pedimentos_created_at', table_name='pedimentos', schema='a76')
|
||||
op.drop_index('idx_pedimentos_client_id', table_name='pedimentos', schema='a76')
|
||||
op.drop_table('pedimentos', schema='a76')
|
||||
op.drop_index(op.f('ix_a76_licenses_tenant_id'), table_name='licenses', schema='a76')
|
||||
op.drop_index(op.f('ix_a76_licenses_id'), table_name='licenses', schema='a76')
|
||||
op.drop_table('licenses', schema='a76')
|
||||
op.drop_index(op.f('ix_a76_license_usage_tenant_id'), table_name='license_usage', schema='a76')
|
||||
op.drop_index(op.f('ix_a76_license_usage_id'), table_name='license_usage', schema='a76')
|
||||
op.drop_table('license_usage', schema='a76')
|
||||
op.drop_index(op.f('ix_a76_tenants_slug'), table_name='tenants', schema='a76')
|
||||
op.drop_index(op.f('ix_a76_tenants_name'), table_name='tenants', schema='a76')
|
||||
op.drop_index(op.f('ix_a76_tenants_id'), table_name='tenants', schema='a76')
|
||||
op.drop_table('tenants', schema='a76')
|
||||
# ### end Alembic commands ###
|
||||
@@ -1,195 +0,0 @@
|
||||
"""Create new A76 tables only - company, clients, parts, classes
|
||||
|
||||
Revision ID: 54f2046774d0
|
||||
Revises: 7937209f9718
|
||||
Create Date: 2025-11-06 03:38:27.848630
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '54f2046774d0'
|
||||
down_revision: Union[str, Sequence[str], None] = '7937209f9718'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema - Create only new A76 tables."""
|
||||
|
||||
# Create new A76 tables only (skip existing tenants, licenses, license_usage)
|
||||
op.create_table('client_provider',
|
||||
sa.Column('client_id', sa.String(length=8), nullable=False),
|
||||
sa.Column('type_nat_foreign', sa.String(length=1), nullable=True),
|
||||
sa.Column('name', sa.String(length=256), nullable=True),
|
||||
sa.Column('short_name', sa.String(length=10), nullable=True),
|
||||
sa.Column('rfc', sa.String(length=30), nullable=True),
|
||||
sa.Column('curp', sa.String(length=19), nullable=True),
|
||||
sa.Column('client_or_provider', sa.String(length=1), nullable=True),
|
||||
sa.Column('linking', sa.String(length=1), nullable=True),
|
||||
sa.Column('transform_subassembly', sa.String(length=1), nullable=True),
|
||||
sa.Column('extra_information', sa.String(length=399), nullable=True),
|
||||
sa.Column('web_key', sa.String(length=40), nullable=True),
|
||||
sa.Column('responsible', sa.String(length=80), nullable=True),
|
||||
sa.Column('position', sa.String(length=30), nullable=True),
|
||||
sa.Column('incoterm', sa.String(length=19), nullable=True),
|
||||
sa.Column('is_national_provider', sa.String(length=2), nullable=True),
|
||||
sa.Column('enabled_disabled', sa.SmallInteger(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('client_id'),
|
||||
schema='a76'
|
||||
)
|
||||
|
||||
op.create_table('gcompany',
|
||||
sa.Column('id', sa.String(length=3), nullable=False),
|
||||
sa.Column('consecutive', sa.Boolean(), nullable=False),
|
||||
sa.Column('name', sa.String(length=255), nullable=True),
|
||||
sa.Column('rfc', sa.String(length=30), nullable=True),
|
||||
sa.Column('main_activity', sa.String(length=255), nullable=True),
|
||||
sa.Column('program', sa.String(length=10), nullable=True),
|
||||
sa.Column('program_number', sa.String(length=40), nullable=True),
|
||||
sa.Column('prosec', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('prosec_authorization', sa.String(length=20), nullable=True),
|
||||
sa.Column('manufacturer_id', sa.String(length=25), nullable=True),
|
||||
sa.Column('broker_company', sa.String(length=10), nullable=True),
|
||||
sa.Column('responsible', sa.String(length=80), nullable=True),
|
||||
sa.Column('responsible_name', sa.String(length=20), nullable=True),
|
||||
sa.Column('responsible_last_name', sa.String(length=20), nullable=True),
|
||||
sa.Column('responsible_mother_last_name', sa.String(length=20), nullable=True),
|
||||
sa.Column('responsible_rfc', sa.String(length=30), nullable=True),
|
||||
sa.Column('position', sa.String(length=30), nullable=True),
|
||||
sa.Column('logo', sa.String(length=255), nullable=True),
|
||||
sa.Column('has_express_line', sa.Boolean(), nullable=True),
|
||||
sa.Column('order_format_type', sa.String(length=19), nullable=True),
|
||||
sa.Column('previous_code', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('is_service_company', sa.Boolean(), nullable=True),
|
||||
sa.Column('client_name', sa.String(length=300), nullable=True),
|
||||
sa.Column('subassembly_mode', sa.String(length=7), nullable=True),
|
||||
sa.Column('curp', sa.String(length=19), nullable=True),
|
||||
sa.Column('inter_db_name', sa.String(length=100), nullable=True),
|
||||
sa.Column('ctpat_svi', sa.String(length=100), nullable=True),
|
||||
sa.Column('trusted_exporter_number', sa.String(length=50), nullable=True),
|
||||
sa.Column('prevalidator_key', sa.String(length=20), nullable=True),
|
||||
sa.Column('seventh_amendment', sa.Boolean(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('consecutive'),
|
||||
schema='a76'
|
||||
)
|
||||
|
||||
op.create_table('classes',
|
||||
sa.Column('client_key', sa.Integer(), nullable=False),
|
||||
sa.Column('class_code', sa.String(length=8), nullable=False),
|
||||
sa.Column('description_spanish', sa.String(length=500), nullable=True),
|
||||
sa.Column('description_english', sa.String(length=500), nullable=True),
|
||||
sa.Column('material_key', sa.String(length=10), nullable=True),
|
||||
sa.Column('unit_of_measure', sa.String(length=5), nullable=True),
|
||||
sa.Column('fraction', sa.String(length=10), nullable=True),
|
||||
sa.Column('us_fraction', sa.String(length=16), nullable=True),
|
||||
sa.Column('sub_key', sa.String(length=5), nullable=True),
|
||||
sa.Column('physical_review', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('iva_exempt_fraction', sa.String(length=4), nullable=True),
|
||||
sa.ForeignKeyConstraint(['material_key'], ['public.material_types.key'], ),
|
||||
sa.PrimaryKeyConstraint('client_key', 'class_code'),
|
||||
schema='a76'
|
||||
)
|
||||
|
||||
op.create_table('parts',
|
||||
sa.Column('client_key', sa.Integer(), nullable=False),
|
||||
sa.Column('part_number', sa.String(length=49), nullable=False),
|
||||
sa.Column('fraction', sa.String(length=10), nullable=True),
|
||||
sa.Column('description_spanish', sa.String(length=500), nullable=True),
|
||||
sa.Column('description_english', sa.String(length=500), nullable=True),
|
||||
sa.Column('part_class', sa.String(length=8), nullable=True),
|
||||
sa.Column('unit_of_measure', sa.String(length=5), nullable=True),
|
||||
sa.Column('commercial_part_number', sa.String(length=70), nullable=True),
|
||||
sa.Column('country_of_origin', sa.String(length=3), nullable=True),
|
||||
sa.Column('unit_cost', sa.Numeric(precision=23, scale=8), nullable=True),
|
||||
sa.Column('currency_type', sa.String(length=2), nullable=True),
|
||||
sa.Column('currency_key', sa.String(length=3), nullable=True),
|
||||
sa.Column('unit_weight', sa.Numeric(precision=19, scale=8), nullable=True),
|
||||
sa.Column('weight_type', sa.String(length=6), nullable=True),
|
||||
sa.Column('us_fraction', sa.String(length=16), nullable=True),
|
||||
sa.Column('fda_key', sa.String(length=20), nullable=True),
|
||||
sa.Column('fcc_key', sa.String(length=30), nullable=True),
|
||||
sa.Column('license_code', sa.String(length=3), nullable=True),
|
||||
sa.Column('eccn', sa.String(length=20), nullable=True),
|
||||
sa.Column('export_code', sa.String(length=2), nullable=True),
|
||||
sa.Column('exclusion_symbol', sa.String(length=19), nullable=True),
|
||||
sa.Column('supplier', sa.String(length=14), nullable=True),
|
||||
sa.Column('alternate_unit_measure', sa.String(length=14), nullable=True),
|
||||
sa.Column('added_value', sa.Numeric(precision=23, scale=8), nullable=True),
|
||||
sa.Column('enabled_disabled', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('creation_date', sa.Integer(), nullable=True),
|
||||
sa.Column('modification_date', sa.Integer(), nullable=True),
|
||||
sa.Column('modification_date_iso', sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column('part_photo', sa.String(length=255), nullable=True),
|
||||
sa.ForeignKeyConstraint(['country_of_origin'], ['public.countries.m3_key'], ),
|
||||
sa.ForeignKeyConstraint(['currency_key'], ['public.currency_types.code'], ),
|
||||
sa.PrimaryKeyConstraint('client_key', 'part_number'),
|
||||
schema='a76'
|
||||
)
|
||||
|
||||
# Create dependent tables after main tables
|
||||
op.create_table('gclient_provider_address',
|
||||
sa.Column('client_id', sa.String(length=8), nullable=False),
|
||||
sa.Column('municipality', sa.String(length=150), nullable=True),
|
||||
sa.Column('streets', sa.String(length=100), nullable=True),
|
||||
sa.Column('neighborhood', sa.String(length=40), nullable=True),
|
||||
sa.Column('interior_number', sa.String(length=20), nullable=True),
|
||||
sa.Column('exterior_number', sa.String(length=20), nullable=True),
|
||||
sa.Column('postal_code', sa.String(length=15), nullable=True),
|
||||
sa.Column('city', sa.String(length=30), nullable=True),
|
||||
sa.Column('state', sa.String(length=30), nullable=True),
|
||||
sa.Column('country', sa.String(length=3), nullable=True),
|
||||
sa.Column('phone', sa.String(length=30), nullable=True),
|
||||
sa.Column('fax_number', sa.String(length=30), nullable=True),
|
||||
sa.Column('email', sa.String(length=100), nullable=True),
|
||||
sa.Column('contact', sa.String(length=50), nullable=True),
|
||||
sa.Column('reference', sa.String(length=250), nullable=True),
|
||||
sa.ForeignKeyConstraint(['client_id'], ['a76.client_provider.client_id'], ondelete='CASCADE'),
|
||||
sa.PrimaryKeyConstraint('client_id'),
|
||||
schema='a76'
|
||||
)
|
||||
|
||||
op.create_table('gclient_provider_programs',
|
||||
sa.Column('client_id', sa.String(length=8), nullable=False),
|
||||
sa.Column('program', sa.String(length=7), nullable=True),
|
||||
sa.Column('program_number', sa.String(length=40), nullable=True),
|
||||
sa.Column('prosec', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('prosec_authorization', sa.String(length=20), nullable=True),
|
||||
sa.Column('secon_auth_date', sa.Integer(), nullable=True),
|
||||
sa.Column('manufacturer_id', sa.String(length=25), nullable=True),
|
||||
sa.Column('tax_id', sa.String(length=30), nullable=True),
|
||||
sa.Column('broker', sa.String(length=6), nullable=True),
|
||||
sa.Column('import_broker', sa.String(length=6), nullable=True),
|
||||
sa.Column('transfer_key', sa.String(length=8), nullable=True),
|
||||
sa.Column('secon_authorization', sa.String(length=20), nullable=True),
|
||||
sa.Column('applied_proportion', sa.Numeric(precision=7, scale=2), nullable=True),
|
||||
sa.Column('is_certified_company', sa.String(length=1), nullable=True),
|
||||
sa.Column('certified_company_registry', sa.String(length=40), nullable=True),
|
||||
sa.Column('donation_auth_number', sa.String(length=50), nullable=True),
|
||||
sa.Column('ctpat_svi', sa.String(length=100), nullable=True),
|
||||
sa.Column('tax_registry_number', sa.String(length=40), nullable=True),
|
||||
sa.Column('subassembly_service', sa.SmallInteger(), nullable=True),
|
||||
sa.Column('autse_dates', sa.Integer(), nullable=True),
|
||||
sa.Column('autse_number', sa.String(length=300), nullable=True),
|
||||
sa.ForeignKeyConstraint(['client_id'], ['a76.client_provider.client_id'], ondelete='CASCADE'),
|
||||
sa.PrimaryKeyConstraint('client_id'),
|
||||
schema='a76'
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema - Drop only new A76 tables."""
|
||||
# Drop tables in reverse dependency order
|
||||
op.drop_table('gclient_provider_programs', schema='a76')
|
||||
op.drop_table('gclient_provider_address', schema='a76')
|
||||
op.drop_table('parts', schema='a76')
|
||||
op.drop_table('classes', schema='a76')
|
||||
op.drop_table('gcompany', schema='a76')
|
||||
op.drop_table('client_provider', schema='a76')
|
||||
@@ -1,11 +1,10 @@
|
||||
"""
|
||||
Modelos ORM para gestión de clientes y proveedores
|
||||
"""
|
||||
from sqlalchemy import Column, Integer, String, DateTime, Boolean, Text, SmallInteger, Numeric, ForeignKey
|
||||
from sqlalchemy import Column, Integer, String, SmallInteger, Numeric, ForeignKey
|
||||
from sqlalchemy.sql import func
|
||||
from sqlalchemy.orm import relationship
|
||||
from core.database import Base
|
||||
import enum
|
||||
|
||||
|
||||
class ClientProvider(Base):
|
||||
@@ -17,7 +16,7 @@ class ClientProvider(Base):
|
||||
|
||||
# Primary key
|
||||
client_id = Column(String(8), primary_key=True, nullable=False)
|
||||
|
||||
|
||||
# Basic information
|
||||
type_nat_foreign = Column(String(1), nullable=True) # TIPO NACIONAL/EXTRANJERO
|
||||
name = Column(String(256), nullable=True)
|
||||
@@ -37,15 +36,15 @@ class ClientProvider(Base):
|
||||
tenant_id = Column(String, ForeignKey("a76.tenants.id"), nullable=False)
|
||||
|
||||
# Relationships
|
||||
address = relationship("GClientProviderAddress", back_populates="client_provider", uselist=False, cascade="all, delete-orphan")
|
||||
programs = relationship("GClientProviderPrograms", back_populates="client_provider", uselist=False, cascade="all, delete-orphan")
|
||||
address = relationship("ClientProviderAddress", back_populates="client_provider", uselist=False, cascade="all, delete-orphan")
|
||||
programs = relationship("ClientProviderPrograms", back_populates="client_provider", uselist=False, cascade="all, delete-orphan")
|
||||
|
||||
|
||||
class GClientProviderAddress(Base):
|
||||
class ClientProviderAddress(Base):
|
||||
"""
|
||||
Modelo para la tabla GClientesPro_Direccion - Dirección de clientes y proveedores
|
||||
"""
|
||||
__tablename__ = "gclient_provider_address"
|
||||
__tablename__ = "client_provider_address"
|
||||
__table_args__ = {"schema": "a76"}
|
||||
|
||||
# Primary key (foreign key)
|
||||
@@ -71,11 +70,11 @@ class GClientProviderAddress(Base):
|
||||
client_provider = relationship("ClientProvider", back_populates="address")
|
||||
|
||||
|
||||
class GClientProviderPrograms(Base):
|
||||
class ClientProviderPrograms(Base):
|
||||
"""
|
||||
Modelo para la tabla GClientesPro_Programas - Programas de clientes y proveedores
|
||||
"""
|
||||
__tablename__ = "gclient_provider_programs"
|
||||
__tablename__ = "client_provider_programs"
|
||||
__table_args__ = {"schema": "a76"}
|
||||
|
||||
# Primary key (foreign key)
|
||||
|
||||
@@ -8,7 +8,7 @@ from fastapi import HTTPException
|
||||
from typing import List, Optional
|
||||
import logging
|
||||
|
||||
from .models import ClientProvider, GClientProviderAddress, GClientProviderPrograms
|
||||
from .models import ClientProvider, ClientProviderAddress, ClientProviderPrograms
|
||||
from .dto import (
|
||||
ClientProviderCreateDTO,
|
||||
ClientProviderUpdateDTO,
|
||||
@@ -72,7 +72,7 @@ class ClientProviderService:
|
||||
|
||||
# Crear dirección si se proporciona
|
||||
if client_data.address:
|
||||
db_address = GClientProviderAddress(
|
||||
db_address = ClientProviderAddress(
|
||||
client_id=client_data.client_id,
|
||||
**client_data.address.model_dump(exclude_unset=True)
|
||||
)
|
||||
@@ -80,7 +80,7 @@ class ClientProviderService:
|
||||
|
||||
# Crear programas si se proporciona
|
||||
if client_data.programs:
|
||||
db_programs = GClientProviderPrograms(
|
||||
db_programs = ClientProviderPrograms(
|
||||
client_id=client_data.client_id,
|
||||
**client_data.programs.model_dump(exclude_unset=True)
|
||||
)
|
||||
@@ -207,7 +207,7 @@ class ClientProviderService:
|
||||
|
||||
# Actualizar dirección
|
||||
if client_data.address:
|
||||
address = self.db.query(GClientProviderAddress).filter(GClientProviderAddress.client_id == client_id).first()
|
||||
address = self.db.query(ClientProviderAddress).filter(ClientProviderAddress.client_id == client_id).first()
|
||||
if address:
|
||||
# Actualizar dirección existente
|
||||
address_data = client_data.address.model_dump(exclude_unset=True)
|
||||
@@ -215,7 +215,7 @@ class ClientProviderService:
|
||||
setattr(address, field, value)
|
||||
else:
|
||||
# Crear nueva dirección
|
||||
address = GClientProviderAddress(
|
||||
address = ClientProviderAddress(
|
||||
client_id=client_id,
|
||||
**client_data.address.model_dump(exclude_unset=True)
|
||||
)
|
||||
@@ -223,7 +223,7 @@ class ClientProviderService:
|
||||
|
||||
# Actualizar programas
|
||||
if client_data.programs:
|
||||
programs = self.db.query(GClientProviderPrograms).filter(GClientProviderPrograms.client_id == client_id).first()
|
||||
programs = self.db.query(ClientProviderPrograms).filter(ClientProviderPrograms.client_id == client_id).first()
|
||||
if programs:
|
||||
# Actualizar programas existentes
|
||||
programs_data = client_data.programs.model_dump(exclude_unset=True)
|
||||
@@ -231,7 +231,7 @@ class ClientProviderService:
|
||||
setattr(programs, field, value)
|
||||
else:
|
||||
# Crear nuevos programas
|
||||
programs = GClientProviderPrograms(
|
||||
programs = ClientProviderPrograms(
|
||||
client_id=client_id,
|
||||
**client_data.programs.model_dump(exclude_unset=True)
|
||||
)
|
||||
|
||||
@@ -7,11 +7,11 @@ from core.database import Base
|
||||
import enum
|
||||
|
||||
|
||||
class GCompany(Base):
|
||||
class Company(Base):
|
||||
"""
|
||||
Modelo para la tabla GCompany - Información de la empresa
|
||||
Modelo para la tabla Company - Información de la empresa
|
||||
"""
|
||||
__tablename__ = "gcompany"
|
||||
__tablename__ = "company"
|
||||
__table_args__ = {"schema": "a76"}
|
||||
|
||||
# Primary key
|
||||
|
||||
@@ -7,7 +7,7 @@ from fastapi import HTTPException
|
||||
from typing import List, Optional
|
||||
import logging
|
||||
|
||||
from .models import GCompany
|
||||
from .models import Company
|
||||
from .dto import CompanyCreateDTO, CompanyUpdateDTO, CompanyResponseDTO
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -34,12 +34,12 @@ class CompanyService:
|
||||
"""
|
||||
try:
|
||||
# Verificar que no exista ya una empresa (solo puede haber una por el consecutivo único)
|
||||
existing = self.db.query(GCompany).filter(GCompany.consecutive == True).first()
|
||||
existing = self.db.query(Company).filter(Company.consecutive == True).first()
|
||||
if existing:
|
||||
raise HTTPException(status_code=400, detail="A company is already registered in the system")
|
||||
|
||||
# Crear empresa
|
||||
db_company = GCompany(
|
||||
db_company = Company(
|
||||
id=company_data.id,
|
||||
consecutive=company_data.consecutive,
|
||||
name=company_data.name,
|
||||
@@ -98,7 +98,7 @@ class CompanyService:
|
||||
Returns:
|
||||
CompanyResponseDTO o None si no existe
|
||||
"""
|
||||
company = self.db.query(GCompany).filter(GCompany.consecutive == True).first()
|
||||
company = self.db.query(Company).filter(Company.consecutive == True).first()
|
||||
if not company:
|
||||
return None
|
||||
return CompanyResponseDTO.model_validate(company)
|
||||
@@ -113,7 +113,7 @@ class CompanyService:
|
||||
Returns:
|
||||
CompanyResponseDTO o None si no existe
|
||||
"""
|
||||
company = self.db.query(GCompany).filter(GCompany.id == company_id).first()
|
||||
company = self.db.query(Company).filter(Company.id == company_id).first()
|
||||
if not company:
|
||||
return None
|
||||
return CompanyResponseDTO.model_validate(company)
|
||||
@@ -129,7 +129,7 @@ class CompanyService:
|
||||
Returns:
|
||||
CompanyResponseDTO actualizada o None si no existe
|
||||
"""
|
||||
company = self.db.query(GCompany).filter(GCompany.id == company_id).first()
|
||||
company = self.db.query(Company).filter(Company.id == company_id).first()
|
||||
if not company:
|
||||
return None
|
||||
|
||||
@@ -158,7 +158,7 @@ class CompanyService:
|
||||
Returns:
|
||||
True si se eliminó, False si no existe
|
||||
"""
|
||||
company = self.db.query(GCompany).filter(GCompany.id == company_id).first()
|
||||
company = self.db.query(Company).filter(Company.id == company_id).first()
|
||||
if not company:
|
||||
return False
|
||||
|
||||
@@ -179,6 +179,6 @@ class CompanyService:
|
||||
Returns:
|
||||
True si existe una empresa, False en caso contrario
|
||||
"""
|
||||
return self.db.query(GCompany).filter(GCompany.consecutive == True).first() is not None
|
||||
return self.db.query(Company).filter(Company.consecutive == True).first() is not None
|
||||
|
||||
|
||||
|
||||
@@ -5,8 +5,6 @@ from datetime import datetime, time
|
||||
|
||||
class PedimentoDatesBase(BaseModel):
|
||||
"""Base schema for Pedimento Dates"""
|
||||
pedimento_id: int = Field(..., description="Pedimento ID")
|
||||
tenant_id: int = Field(..., description="Tenant ID")
|
||||
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")
|
||||
@@ -45,6 +43,8 @@ class PedimentoDatesUpdate(BaseModel):
|
||||
class PedimentoDatesResponse(PedimentoDatesBase):
|
||||
"""Schema for Pedimento Dates response"""
|
||||
id: int
|
||||
pedimento_id: int = Field(..., description="Pedimento ID")
|
||||
tenant_id: int = Field(..., description="Tenant ID")
|
||||
created_at: datetime
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
@@ -11,7 +11,7 @@ class PedimentoRectificationOriginBase(BaseModel):
|
||||
original_customs_office: Optional[str] = Field(None, max_length=3, description="Original customs office")
|
||||
original_license: Optional[str] = Field(None, max_length=4, description="Original license")
|
||||
original_pedimento_number: Optional[str] = Field(None, max_length=7, description="Original pedimento number")
|
||||
original_pedimento_key: Optional[str] = Field(None, max_length=2, description="Original pedimento key")
|
||||
original_pedimento_code: Optional[str] = Field(None, max_length=2, description="Original pedimento key")
|
||||
original_payment_date: Optional[datetime] = Field(None, description="Original payment date")
|
||||
total_cash: Optional[int] = Field(None, description="Total cash")
|
||||
total_others: Optional[int] = Field(None, description="Total others")
|
||||
@@ -33,7 +33,7 @@ class PedimentoRectificationOriginUpdate(BaseModel):
|
||||
original_customs_office: Optional[str] = Field(None, max_length=3)
|
||||
original_license: Optional[str] = Field(None, max_length=4)
|
||||
original_pedimento_number: Optional[str] = Field(None, max_length=7)
|
||||
original_pedimento_key: Optional[str] = Field(None, max_length=2)
|
||||
original_pedimento_code: Optional[str] = Field(None, max_length=2)
|
||||
original_payment_date: Optional[datetime] = None
|
||||
total_cash: Optional[int] = None
|
||||
total_others: Optional[int] = None
|
||||
|
||||
@@ -13,7 +13,7 @@ class PedimentosBase(BaseModel):
|
||||
client_id: Optional[int] = Field(None, description="Client ID")
|
||||
operation_type: Optional[int] = Field(None, description="Operation type")
|
||||
pedimento_type: Optional[int] = Field(None, description="Pedimento type")
|
||||
pedimento_key: Optional[str] = Field(None, max_length=2, description="Pedimento key")
|
||||
pedimento_code: Optional[str] = Field(None, max_length=2, description="Pedimento key")
|
||||
regime: Optional[str] = Field(None, max_length=3, description="Regime")
|
||||
status: Optional[str] = Field(None, max_length=30, description="Status")
|
||||
usd_value: Optional[Decimal] = Field(None, description="USD value")
|
||||
@@ -36,7 +36,7 @@ class PedimentosUpdate(BaseModel):
|
||||
client_id: Optional[int] = None
|
||||
operation_type: Optional[int] = None
|
||||
pedimento_type: Optional[int] = None
|
||||
pedimento_key: Optional[str] = Field(None, max_length=2)
|
||||
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
|
||||
|
||||
@@ -20,7 +20,7 @@ class PedimentoRectificationOrigin(Base):
|
||||
original_customs_office = mapped_column(String(3))
|
||||
original_license = mapped_column(String(4))
|
||||
original_pedimento_number = mapped_column(String(7))
|
||||
original_pedimento_key = mapped_column(String(2))
|
||||
original_pedimento_code = mapped_column(String(2))
|
||||
original_payment_date = mapped_column(DateTime)
|
||||
total_cash = mapped_column(Integer)
|
||||
total_others = mapped_column(Integer)
|
||||
|
||||
@@ -7,6 +7,8 @@ from core.database import Base
|
||||
class Pedimentos(Base):
|
||||
__tablename__ = 'pedimentos'
|
||||
__table_args__ = (
|
||||
ForeignKeyConstraint(['regime'], ['public.pedimento_regimens.code']),
|
||||
ForeignKeyConstraint(['pedimento_code'], ['public.pedimento_codes.code']),
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id']),
|
||||
PrimaryKeyConstraint('id', name='pedimentos_pkey'),
|
||||
Index('idx_pedimentos_client_id', 'client_id'),
|
||||
@@ -24,7 +26,7 @@ class Pedimentos(Base):
|
||||
client_id = mapped_column(Integer)
|
||||
operation_type = mapped_column(Integer)
|
||||
pedimento_type = mapped_column(Integer)
|
||||
pedimento_key = mapped_column(String(2))
|
||||
pedimento_code = mapped_column(String(2))
|
||||
regime = mapped_column(String(3))
|
||||
status = mapped_column(String(30))
|
||||
usd_value = mapped_column(Numeric(17, 6))
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Routes for PedimentoDates CRUD operations
|
||||
"""
|
||||
import logging
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
from core.database import get_core_db
|
||||
@@ -15,7 +16,7 @@ from ..dtos.pedimento_dates import (
|
||||
|
||||
|
||||
router = APIRouter(prefix="/{pedimento_id}/dates")
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@router.get("/", response_model=PedimentoDatesResponse)
|
||||
async def get_dates(
|
||||
@@ -36,8 +37,7 @@ async def get_dates(
|
||||
|
||||
|
||||
@router.post("/", response_model=PedimentoDatesResponse, status_code=201)
|
||||
async def create_dates(
|
||||
pedimento_id: int,
|
||||
async def create_dates(
|
||||
data: PedimentoDatesCreate,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
@@ -46,11 +46,7 @@ async def create_dates(
|
||||
tenant_id = get_tenant_from_token(current_user)
|
||||
if not tenant_id:
|
||||
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
|
||||
|
||||
# Ensure pedimento_id matches
|
||||
if data.pedimento_id != pedimento_id:
|
||||
raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
|
||||
|
||||
|
||||
dates = PedimentoDatesService.create(db, data, tenant_id)
|
||||
return dates
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
"""
|
||||
Service layer for PedimentoDates CRUD operations
|
||||
"""
|
||||
import logging
|
||||
from typing import Optional
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ..models.pedimento_dates import PedimentoDates
|
||||
from ..dtos.pedimento_dates import PedimentoDatesCreate, PedimentoDatesUpdate
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class PedimentoDatesService:
|
||||
"""Service class for PedimentoDates business logic"""
|
||||
|
||||
@@ -12,6 +12,7 @@ from core.middleware import (
|
||||
LicenseValidationMiddleware,
|
||||
RequestLoggingMiddleware
|
||||
)
|
||||
from core.database import init_db
|
||||
from api.v1.router import router as api_v1_router
|
||||
|
||||
# Configurar logging
|
||||
@@ -26,12 +27,20 @@ logger = logging.getLogger(__name__)
|
||||
app = FastAPI(
|
||||
title="Anexo76 API",
|
||||
version=settings.APP_VERSION,
|
||||
description="Aplicación SaaS para gestión de comercio exterior conforme a Anexos 24, 31 y 22 del SAT",
|
||||
description="Aplicación SaaS para gestión de comercio exterior conforme a Anexos 24, 30 y 22 del SAT",
|
||||
docs_url="/api/docs" if settings.DEBUG else None,
|
||||
redoc_url="/api/redoc" if settings.DEBUG else None,
|
||||
openapi_url="/api/openapi.json" if settings.DEBUG else None,
|
||||
)
|
||||
|
||||
# Inicializar la base de datos
|
||||
@app.on_event("startup")
|
||||
async def on_startup():
|
||||
"""Evento de inicio de la aplicación"""
|
||||
logger.info("Iniciando la aplicación Anexo76...")
|
||||
init_db()
|
||||
logger.info("Base de datos inicializada correctamente.")
|
||||
|
||||
# Configurar CORS
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
@@ -41,6 +50,8 @@ app.add_middleware(
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
logger.info(f"CORS configurado para orígenes: {settings.cors_origins_list}")
|
||||
|
||||
# Agregar middlewares personalizados
|
||||
app.add_middleware(RequestLoggingMiddleware)
|
||||
app.add_middleware(LicenseValidationMiddleware)
|
||||
|
||||
@@ -162,7 +162,7 @@ services:
|
||||
- KEYCLOAK_REALM=${KEYCLOAK_REALM:-master}
|
||||
- KEYCLOAK_CLIENT_ID=${KEYCLOAK_CLIENT_ID:-anexo76-backend}
|
||||
- KEYCLOAK_CLIENT_SECRET=${KEYCLOAK_CLIENT_SECRET:-dev-secret}
|
||||
- CORS_ORIGINS=http://localhost:5180,http://localhost:3000
|
||||
- CORS_ORIGINS=${CORS_ORIGINS:-http://localhost:5180,http://localhost:3000}
|
||||
ports:
|
||||
- "8000:8000"
|
||||
depends_on:
|
||||
|
||||
@@ -12,10 +12,10 @@ Se han actualizado todos los modelos en `api/v1/modules/a76/` para usar el schem
|
||||
|
||||
| Módulo | Tabla | Schema | Estado |
|
||||
|--------|-------|---------|---------|
|
||||
| **Company** | `gcompany` | `a76` | ✅ Actualizada |
|
||||
| **Company** | `company` | `a76` | ✅ Actualizada |
|
||||
| **Client & Provider** | `client_provider` | `a76` | ✅ Actualizada |
|
||||
| **Client & Provider** | `gclient_provider_address` | `a76` | ✅ Actualizada |
|
||||
| **Client & Provider** | `gclient_provider_programs` | `a76` | ✅ Actualizada |
|
||||
| **Client & Provider** | `client_provider_address` | `a76` | ✅ Actualizada |
|
||||
| **Client & Provider** | `client_provider_programs` | `a76` | ✅ Actualizada |
|
||||
| **GParts** | `parts` | `a76` | ✅ Actualizada |
|
||||
| **Class** | `classes` | `a76` | ✅ Actualizada |
|
||||
| **Licenses** | `licenses` | `a76` | ✅ Ya estaba |
|
||||
@@ -27,12 +27,12 @@ Se han actualizado todos los modelos en `api/v1/modules/a76/` para usar el schem
|
||||
#### 1. Configuración de Schema
|
||||
```python
|
||||
# ANTES
|
||||
class GCompany(Base):
|
||||
__tablename__ = "gcompany"
|
||||
class Company(Base):
|
||||
__tablename__ = "company"
|
||||
|
||||
# DESPUÉS
|
||||
class GCompany(Base):
|
||||
__tablename__ = "gcompany"
|
||||
class Company(Base):
|
||||
__tablename__ = "company"
|
||||
__table_args__ = {"schema": "a76"}
|
||||
```
|
||||
|
||||
@@ -59,10 +59,10 @@ PostgreSQL Database
|
||||
├── tenants
|
||||
├── licenses
|
||||
├── license_usage
|
||||
├── gcompany
|
||||
├── company
|
||||
├── client_provider
|
||||
├── gclient_provider_address
|
||||
├── gclient_provider_programs
|
||||
├── client_provider_address
|
||||
├── client_provider_programs
|
||||
├── parts
|
||||
└── classes
|
||||
```
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"context": {
|
||||
"project_name": "Anexo76",
|
||||
"description": "Aplicación SaaS para gestión de comercio exterior conforme a Anexos 24, 31 y 22 del SAT.",
|
||||
"description": "Aplicación SaaS para gestión de comercio exterior conforme a Anexos 24, 30 y 22 del SAT.",
|
||||
"business_goal": "Ofrecer una plataforma multi-tenant para maquilas, IMMEX y agentes aduanales que permita manejar inventarios, pedimentos y facturas de importación/exportación con control de licencias y cumplimiento normativo."
|
||||
},
|
||||
"architecture": {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Anexo76 - Gestión de Comercio Exterior</title>
|
||||
<meta name="description" content="Plataforma SaaS para gestión de comercio exterior conforme a Anexos 24, 31 y 22 del SAT" />
|
||||
<meta name="description" content="Plataforma SaaS para gestión de comercio exterior conforme a Anexos 24, 30 y 22 del SAT" />
|
||||
<script>
|
||||
// Cargar el tema antes de renderizar para evitar flash
|
||||
(function() {
|
||||
|
||||
67
frontend/src/lib/api/dashboard/a76/pedimento-dates.ts
Normal file
67
frontend/src/lib/api/dashboard/a76/pedimento-dates.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* API Client para Fechas de Pedimentos
|
||||
*/
|
||||
import { api } from '$lib/api';
|
||||
|
||||
export interface PedimentoDates {
|
||||
id: number;
|
||||
pedimento_id: number;
|
||||
tenant_id: number;
|
||||
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;
|
||||
capture_date?: string | null;
|
||||
capture_time?: string | null;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface CreatePedimentoDatesData {
|
||||
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;
|
||||
capture_date?: string | null;
|
||||
capture_time?: string | null;
|
||||
}
|
||||
|
||||
export interface UpdatePedimentoDatesData {
|
||||
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;
|
||||
capture_date?: string | null;
|
||||
capture_time?: string | null;
|
||||
}
|
||||
|
||||
export const pedimentoDatesApi = {
|
||||
get: (pedimentoId: number) =>
|
||||
api.get<PedimentoDates>(`/v1/a76/pedimentos/${pedimentoId}/dates`),
|
||||
|
||||
create: (pedimentoId: number, data: CreatePedimentoDatesData) =>
|
||||
api.post<PedimentoDates>(`/v1/a76/pedimentos/${pedimentoId}/dates`, data),
|
||||
|
||||
update: (pedimentoId: number, data: UpdatePedimentoDatesData) =>
|
||||
api.put<PedimentoDates>(`/v1/a76/pedimentos/${pedimentoId}/dates`, data),
|
||||
|
||||
delete: (pedimentoId: number) =>
|
||||
api.delete(`/v1/a76/pedimentos/${pedimentoId}/dates`)
|
||||
};
|
||||
67
frontend/src/lib/api/dashboard/a76/pedimento-payments.ts
Normal file
67
frontend/src/lib/api/dashboard/a76/pedimento-payments.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* API Client para Pagos de Pedimentos
|
||||
*/
|
||||
import { api } from '$lib/api';
|
||||
|
||||
export interface PedimentoPayments {
|
||||
id: number;
|
||||
pedimento_id: number;
|
||||
tenant_id: number;
|
||||
acknowledgment?: string | null;
|
||||
operation_number?: string | null;
|
||||
bank_code?: number | null;
|
||||
cashier?: string | null;
|
||||
date?: string | null;
|
||||
time?: string | null;
|
||||
shift?: string | null;
|
||||
total_cash_paid?: number | null;
|
||||
total_contributions?: number | null;
|
||||
counter_payment?: number | null;
|
||||
pece_code?: string | null;
|
||||
payment_id?: number | null;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface CreatePedimentoPaymentsData {
|
||||
acknowledgment?: string | null;
|
||||
operation_number?: string | null;
|
||||
bank_code?: number | null;
|
||||
cashier?: string | null;
|
||||
date?: string | null;
|
||||
time?: string | null;
|
||||
shift?: string | null;
|
||||
total_cash_paid?: number | null;
|
||||
total_contributions?: number | null;
|
||||
counter_payment?: number | null;
|
||||
pece_code?: string | null;
|
||||
payment_id?: number | null;
|
||||
}
|
||||
|
||||
export interface UpdatePedimentoPaymentsData {
|
||||
acknowledgment?: string | null;
|
||||
operation_number?: string | null;
|
||||
bank_code?: number | null;
|
||||
cashier?: string | null;
|
||||
date?: string | null;
|
||||
time?: string | null;
|
||||
shift?: string | null;
|
||||
total_cash_paid?: number | null;
|
||||
total_contributions?: number | null;
|
||||
counter_payment?: number | null;
|
||||
pece_code?: string | null;
|
||||
payment_id?: number | null;
|
||||
}
|
||||
|
||||
export const pedimentoPaymentsApi = {
|
||||
get: (pedimentoId: number) =>
|
||||
api.get<PedimentoPayments>(`/v1/a76/pedimentos/${pedimentoId}/payments`),
|
||||
|
||||
create: (pedimentoId: number, data: CreatePedimentoPaymentsData) =>
|
||||
api.post<PedimentoPayments>(`/v1/a76/pedimentos/${pedimentoId}/payments`, data),
|
||||
|
||||
update: (pedimentoId: number, data: UpdatePedimentoPaymentsData) =>
|
||||
api.put<PedimentoPayments>(`/v1/a76/pedimentos/${pedimentoId}/payments`, data),
|
||||
|
||||
delete: (pedimentoId: number) =>
|
||||
api.delete(`/v1/a76/pedimentos/${pedimentoId}/payments`)
|
||||
};
|
||||
43
frontend/src/lib/api/dashboard/a76/pedimento-transport.ts
Normal file
43
frontend/src/lib/api/dashboard/a76/pedimento-transport.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* API Client para Medios de Transporte de Pedimentos
|
||||
*/
|
||||
import { api } from '$lib/api';
|
||||
|
||||
export interface PedimentoTransportMeans {
|
||||
id: number;
|
||||
pedimento_id: number;
|
||||
tenant_id: number;
|
||||
destination?: number | null;
|
||||
entry_exit?: string | null;
|
||||
arrival?: string | null;
|
||||
departure?: string | null;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface CreatePedimentoTransportMeansData {
|
||||
destination?: number | null;
|
||||
entry_exit?: string | null;
|
||||
arrival?: string | null;
|
||||
departure?: string | null;
|
||||
}
|
||||
|
||||
export interface UpdatePedimentoTransportMeansData {
|
||||
destination?: number | null;
|
||||
entry_exit?: string | null;
|
||||
arrival?: string | null;
|
||||
departure?: string | null;
|
||||
}
|
||||
|
||||
export const pedimentoTransportApi = {
|
||||
get: (pedimentoId: number) =>
|
||||
api.get<PedimentoTransportMeans>(`/v1/a76/pedimentos/${pedimentoId}/transport-means`),
|
||||
|
||||
create: (pedimentoId: number, data: CreatePedimentoTransportMeansData) =>
|
||||
api.post<PedimentoTransportMeans>(`/v1/a76/pedimentos/${pedimentoId}/transport-means`, data),
|
||||
|
||||
update: (pedimentoId: number, data: UpdatePedimentoTransportMeansData) =>
|
||||
api.put<PedimentoTransportMeans>(`/v1/a76/pedimentos/${pedimentoId}/transport-means`, data),
|
||||
|
||||
delete: (pedimentoId: number) =>
|
||||
api.delete(`/v1/a76/pedimentos/${pedimentoId}/transport-means`)
|
||||
};
|
||||
55
frontend/src/lib/api/dashboard/a76/pedimento-validation.ts
Normal file
55
frontend/src/lib/api/dashboard/a76/pedimento-validation.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* API Client para Validación de Pedimentos
|
||||
*/
|
||||
import { api } from '$lib/api';
|
||||
|
||||
export interface PedimentoValidation {
|
||||
id: number;
|
||||
pedimento_id: number;
|
||||
tenant_id: number;
|
||||
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;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface CreatePedimentoValidationData {
|
||||
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 UpdatePedimentoValidationData {
|
||||
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 const pedimentoValidationApi = {
|
||||
get: (pedimentoId: number) =>
|
||||
api.get<PedimentoValidation>(`/v1/a76/pedimentos/${pedimentoId}/validation`),
|
||||
|
||||
create: (pedimentoId: number, data: CreatePedimentoValidationData) =>
|
||||
api.post<PedimentoValidation>(`/v1/a76/pedimentos/${pedimentoId}/validation`, data),
|
||||
|
||||
update: (pedimentoId: number, data: UpdatePedimentoValidationData) =>
|
||||
api.put<PedimentoValidation>(`/v1/a76/pedimentos/${pedimentoId}/validation`, data),
|
||||
|
||||
delete: (pedimentoId: number) =>
|
||||
api.delete(`/v1/a76/pedimentos/${pedimentoId}/validation`)
|
||||
};
|
||||
@@ -14,7 +14,7 @@ export interface Pedimento {
|
||||
client_id?: number | null;
|
||||
operation_type?: number | null;
|
||||
pedimento_type?: number | null;
|
||||
pedimento_key?: string | null;
|
||||
pedimento_code?: string | null;
|
||||
regime?: string | null;
|
||||
status?: string | null;
|
||||
usd_value?: number | null;
|
||||
@@ -39,7 +39,7 @@ export interface CreatePedimentoData {
|
||||
client_id?: number | null;
|
||||
operation_type?: number | null;
|
||||
pedimento_type?: number | null;
|
||||
pedimento_key?: string | null;
|
||||
pedimento_code?: string | null;
|
||||
regime?: string | null;
|
||||
status?: string | null;
|
||||
usd_value?: number | null;
|
||||
@@ -56,7 +56,7 @@ export interface UpdatePedimentoData {
|
||||
client_id?: number | null;
|
||||
operation_type?: number | null;
|
||||
pedimento_type?: number | null;
|
||||
pedimento_key?: string | null;
|
||||
pedimento_code?: string | null;
|
||||
regime?: string | null;
|
||||
status?: string | null;
|
||||
usd_value?: number | null;
|
||||
|
||||
@@ -13,7 +13,7 @@ export type Pedimento = {
|
||||
client_id?: number | null;
|
||||
operation_type?: number | null;
|
||||
pedimento_type?: number | null;
|
||||
pedimento_key?: string | null;
|
||||
pedimento_code?: string | null;
|
||||
regime?: string | null;
|
||||
status?: string | null;
|
||||
usd_value?: number | null;
|
||||
@@ -67,15 +67,40 @@ function formatDate(date?: string | null): string {
|
||||
function getStatusColor(status?: string | null): string {
|
||||
if (!status) return 'bg-gray-100 text-gray-800';
|
||||
|
||||
const statusLower = status.toLowerCase();
|
||||
if (statusLower.includes('activo') || statusLower.includes('completado')) {
|
||||
const statusUpper = status.toUpperCase();
|
||||
|
||||
// Estados completados/exitosos - Verde
|
||||
if (statusUpper === 'VALIDADO' || statusUpper === 'PAGADO' || statusUpper === 'CARTA CUPO') {
|
||||
return 'bg-green-100 text-green-800';
|
||||
} else if (statusLower.includes('pendiente') || statusLower.includes('proceso')) {
|
||||
}
|
||||
|
||||
// Estados en espera/proceso - Amarillo
|
||||
if (statusUpper.startsWith('ESPERA')) {
|
||||
return 'bg-yellow-100 text-yellow-800';
|
||||
} else if (statusLower.includes('cancelado') || statusLower.includes('rechazado')) {
|
||||
}
|
||||
|
||||
// Estados con firma - Azul
|
||||
if (statusUpper === 'CON FIRMA DE PREVIO') {
|
||||
return 'bg-blue-100 text-blue-800';
|
||||
}
|
||||
|
||||
// Estados modificables/editables - Índigo
|
||||
if (statusUpper === 'MODIFICABLE') {
|
||||
return 'bg-indigo-100 text-indigo-800';
|
||||
}
|
||||
|
||||
// Estados de borrado - Naranja
|
||||
if (statusUpper.includes('BORRADA')) {
|
||||
return 'bg-orange-100 text-orange-800';
|
||||
}
|
||||
|
||||
// Estados cancelados/desistidos - Rojo
|
||||
if (statusUpper === 'DESISTIO') {
|
||||
return 'bg-red-100 text-red-800';
|
||||
}
|
||||
return 'bg-blue-100 text-blue-800';
|
||||
|
||||
// Default - Gris
|
||||
return 'bg-gray-100 text-gray-800';
|
||||
}
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<Pedimento>[] {
|
||||
@@ -99,7 +124,7 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Pedimento>[] {
|
||||
header: "Número de Pedimento",
|
||||
cell: ({ row }) => {
|
||||
const pedimento = row.original;
|
||||
const fullNumber = `${pedimento.year || ''}${pedimento.customs_office || ''}${pedimento.license || ''}${pedimento.pedimento_number || ''}`;
|
||||
const fullNumber = `${pedimento.year || ''}-${pedimento.customs_office || ''}-${pedimento.license || ''}-${pedimento.pedimento_number || ''}`;
|
||||
|
||||
const numberSnippet = createRawSnippet<[{ number: string }]>((getNumber) => {
|
||||
const { number } = getNumber();
|
||||
@@ -131,17 +156,20 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Pedimento>[] {
|
||||
cell: ({ row }) => {
|
||||
const status = row.original.status;
|
||||
const colorClass = getStatusColor(status);
|
||||
const formattedStatus = status
|
||||
? status.charAt(0).toUpperCase() + status.slice(1).toLowerCase()
|
||||
: 'N/A';
|
||||
|
||||
const statusSnippet = createRawSnippet<[{ status?: string | null; colorClass: string }]>((getStatus) => {
|
||||
const statusSnippet = createRawSnippet<[{ status: string; colorClass: string }]>((getStatus) => {
|
||||
const { status, colorClass } = getStatus();
|
||||
return {
|
||||
render: () =>
|
||||
`<span class="inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ${colorClass}">
|
||||
${status || 'N/A'}
|
||||
${status}
|
||||
</span>`
|
||||
};
|
||||
});
|
||||
return renderSnippet(statusSnippet, { status, colorClass });
|
||||
return renderSnippet(statusSnippet, { status: formattedStatus, colorClass });
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,425 +0,0 @@
|
||||
<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 { pedimentosApi, type Pedimento, type CreatePedimentoData, type UpdatePedimentoData } from "$lib/api/dashboard/a76/pedimentos";
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
item = $bindable<Pedimento | null>(null),
|
||||
onSuccess
|
||||
}: {
|
||||
open: boolean;
|
||||
item?: Pedimento | null;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
let formData = $state({
|
||||
year: "",
|
||||
customs_office: "",
|
||||
license: "",
|
||||
pedimento_number: "",
|
||||
client_id: null as number | null,
|
||||
operation_type: null as number | null,
|
||||
pedimento_type: null as number | null,
|
||||
pedimento_key: "",
|
||||
regime: "",
|
||||
status: "",
|
||||
usd_value: null as number | null,
|
||||
paid_price: null as number | null,
|
||||
gross_weight: null as number | null,
|
||||
exchange_rate: null as number | null
|
||||
});
|
||||
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
// Actualizar formData cuando item cambia
|
||||
$effect(() => {
|
||||
if (item) {
|
||||
formData = {
|
||||
year: item.year || "",
|
||||
customs_office: item.customs_office || "",
|
||||
license: item.license || "",
|
||||
pedimento_number: item.pedimento_number || "",
|
||||
client_id: item.client_id ?? null,
|
||||
operation_type: item.operation_type ?? null,
|
||||
pedimento_type: item.pedimento_type ?? null,
|
||||
pedimento_key: item.pedimento_key || "",
|
||||
regime: item.regime || "",
|
||||
status: item.status || "",
|
||||
usd_value: item.usd_value ?? null,
|
||||
paid_price: item.paid_price ?? null,
|
||||
gross_weight: item.gross_weight ?? null,
|
||||
exchange_rate: item.exchange_rate ?? null
|
||||
};
|
||||
} else {
|
||||
formData = {
|
||||
year: "",
|
||||
customs_office: "",
|
||||
license: "",
|
||||
pedimento_number: "",
|
||||
client_id: null,
|
||||
operation_type: null,
|
||||
pedimento_type: null,
|
||||
pedimento_key: "",
|
||||
regime: "",
|
||||
status: "",
|
||||
usd_value: null,
|
||||
paid_price: null,
|
||||
gross_weight: null,
|
||||
exchange_rate: null
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const isEditing = $derived(!!item);
|
||||
|
||||
// Estados comunes
|
||||
const statusOptions = [
|
||||
{ value: "Activo", label: "Activo" },
|
||||
{ value: "Pendiente", label: "Pendiente" },
|
||||
{ value: "En Proceso", label: "En Proceso" },
|
||||
{ value: "Completado", label: "Completado" },
|
||||
{ value: "Cancelado", label: "Cancelado" }
|
||||
];
|
||||
|
||||
async function handleSubmit(e: Event) {
|
||||
e.preventDefault();
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
let response;
|
||||
if (isEditing && item) {
|
||||
const payload: UpdatePedimentoData = {
|
||||
year: formData.year || null,
|
||||
customs_office: formData.customs_office || null,
|
||||
license: formData.license || null,
|
||||
pedimento_number: formData.pedimento_number || null,
|
||||
client_id: formData.client_id,
|
||||
operation_type: formData.operation_type,
|
||||
pedimento_type: formData.pedimento_type,
|
||||
pedimento_key: formData.pedimento_key || null,
|
||||
regime: formData.regime || null,
|
||||
status: formData.status || null,
|
||||
usd_value: formData.usd_value,
|
||||
paid_price: formData.paid_price,
|
||||
gross_weight: formData.gross_weight,
|
||||
exchange_rate: formData.exchange_rate
|
||||
};
|
||||
response = await pedimentosApi.update(item.id, payload);
|
||||
} else {
|
||||
const payload: CreatePedimentoData = {
|
||||
year: formData.year || null,
|
||||
customs_office: formData.customs_office || null,
|
||||
license: formData.license || null,
|
||||
pedimento_number: formData.pedimento_number || null,
|
||||
client_id: formData.client_id,
|
||||
operation_type: formData.operation_type,
|
||||
pedimento_type: formData.pedimento_type,
|
||||
pedimento_key: formData.pedimento_key || null,
|
||||
regime: formData.regime || null,
|
||||
status: formData.status || null,
|
||||
usd_value: formData.usd_value,
|
||||
paid_price: formData.paid_price,
|
||||
gross_weight: formData.gross_weight,
|
||||
exchange_rate: formData.exchange_rate
|
||||
};
|
||||
response = await pedimentosApi.create(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 saving:", e);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleOpenChange(newOpen: boolean) {
|
||||
if (!newOpen) {
|
||||
// Limpiar form al cerrar
|
||||
formData = {
|
||||
year: "",
|
||||
customs_office: "",
|
||||
license: "",
|
||||
pedimento_number: "",
|
||||
client_id: null,
|
||||
operation_type: null,
|
||||
pedimento_type: null,
|
||||
pedimento_key: "",
|
||||
regime: "",
|
||||
status: "",
|
||||
usd_value: null,
|
||||
paid_price: null,
|
||||
gross_weight: null,
|
||||
exchange_rate: null
|
||||
};
|
||||
error = null;
|
||||
}
|
||||
open = newOpen;
|
||||
}
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open onOpenChange={handleOpenChange}>
|
||||
<Dialog.Content class="sm:max-w-[700px] max-h-[90vh] overflow-y-auto">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>
|
||||
{isEditing ? "Editar" : "Nuevo"} Pedimento
|
||||
</Dialog.Title>
|
||||
<Dialog.Description>
|
||||
{isEditing
|
||||
? "Modifica los datos del pedimento."
|
||||
: "Completa los datos para crear un nuevo pedimento."}
|
||||
</Dialog.Description>
|
||||
</Dialog.Header>
|
||||
|
||||
<form onsubmit={handleSubmit} class="space-y-4">
|
||||
{#if error}
|
||||
<div class="rounded-lg border border-destructive bg-destructive/10 p-3 text-sm text-destructive">
|
||||
{error}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Información del Pedimento -->
|
||||
<div class="space-y-4">
|
||||
<h3 class="text-sm font-medium">Información del Pedimento</h3>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="year">Año</Label>
|
||||
<Input
|
||||
id="year"
|
||||
bind:value={formData.year}
|
||||
placeholder="22"
|
||||
maxlength={2}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="customs_office">Aduana</Label>
|
||||
<Input
|
||||
id="customs_office"
|
||||
bind:value={formData.customs_office}
|
||||
placeholder="01"
|
||||
maxlength={2}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="license">Patente</Label>
|
||||
<Input
|
||||
id="license"
|
||||
bind:value={formData.license}
|
||||
placeholder="3001"
|
||||
maxlength={4}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="pedimento_number">Número de Pedimento</Label>
|
||||
<Input
|
||||
id="pedimento_number"
|
||||
bind:value={formData.pedimento_number}
|
||||
placeholder="0001234"
|
||||
maxlength={7}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="pedimento_key">Clave de Pedimento</Label>
|
||||
<Input
|
||||
id="pedimento_key"
|
||||
bind:value={formData.pedimento_key}
|
||||
placeholder="A1"
|
||||
maxlength={2}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="regime">Régimen</Label>
|
||||
<Input
|
||||
id="regime"
|
||||
bind:value={formData.regime}
|
||||
placeholder="IMD"
|
||||
maxlength={3}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Información del Cliente y Operación -->
|
||||
<div class="space-y-4">
|
||||
<h3 class="text-sm font-medium">Cliente y Operación</h3>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<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="123"
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="status">Estado</Label>
|
||||
<select
|
||||
id="status"
|
||||
bind:value={formData.status}
|
||||
disabled={loading}
|
||||
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 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
<option value="">Seleccionar estado</option>
|
||||
{#each statusOptions as option}
|
||||
<option value={option.value}>{option.label}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<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="1"
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="pedimento_type">Tipo de Pedimento</Label>
|
||||
<Input
|
||||
id="pedimento_type"
|
||||
type="number"
|
||||
bind:value={formData.pedimento_type}
|
||||
placeholder="1"
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Información Financiera -->
|
||||
<div class="space-y-4">
|
||||
<h3 class="text-sm font-medium">Información Financiera</h3>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="usd_value">Valor en USD</Label>
|
||||
<Input
|
||||
id="usd_value"
|
||||
type="number"
|
||||
step="0.01"
|
||||
bind:value={formData.usd_value}
|
||||
placeholder="1000.00"
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="paid_price">Precio Pagado</Label>
|
||||
<Input
|
||||
id="paid_price"
|
||||
type="number"
|
||||
step="0.01"
|
||||
bind:value={formData.paid_price}
|
||||
placeholder="1000.00"
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="exchange_rate">Tipo de Cambio</Label>
|
||||
<Input
|
||||
id="exchange_rate"
|
||||
type="number"
|
||||
step="0.00001"
|
||||
bind:value={formData.exchange_rate}
|
||||
placeholder="19.50000"
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="gross_weight">Peso Bruto (kg)</Label>
|
||||
<Input
|
||||
id="gross_weight"
|
||||
type="number"
|
||||
step="0.001"
|
||||
bind:value={formData.gross_weight}
|
||||
placeholder="100.000"
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Dialog.Footer>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onclick={() => (open = false)}
|
||||
disabled={loading}
|
||||
>
|
||||
Cancelar
|
||||
</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>
|
||||
{/if}
|
||||
{isEditing ? "Guardar cambios" : "Crear pedimento"}
|
||||
</Button>
|
||||
</Dialog.Footer>
|
||||
</form>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
@@ -11,8 +11,6 @@
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
let showEditDialog = $state(false);
|
||||
let showDeleteDialog = $state(false);
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
@@ -54,12 +52,8 @@
|
||||
}
|
||||
|
||||
function handleEdit() {
|
||||
showEditDialog = true;
|
||||
}
|
||||
|
||||
function handleView() {
|
||||
// Navegar a la vista de detalles
|
||||
window.location.href = `/dashboard/pedimentos/${item.id}`;
|
||||
// Navegar a la página de edición
|
||||
window.location.href = `/dashboard/pedimentos/edit/${item.id}`;
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -89,24 +83,6 @@
|
||||
<DropdownMenu.Content align="end" class="w-[160px]">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleView}>
|
||||
<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="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z" />
|
||||
<circle cx="12" cy="12" r="3" />
|
||||
</svg>
|
||||
Ver detalles
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={handleEdit}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -170,10 +146,3 @@
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
{#if showEditDialog}
|
||||
<!-- Importar dinámicamente el componente de edición cuando se necesite -->
|
||||
{#await import('./create-edit-dialog.svelte') then { default: CreateEditDialog }}
|
||||
<CreateEditDialog bind:open={showEditDialog} bind:item onSuccess={onSuccess} />
|
||||
{/await}
|
||||
{/if}
|
||||
|
||||
@@ -0,0 +1,248 @@
|
||||
<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';
|
||||
|
||||
let {
|
||||
pedimentoId,
|
||||
formData = $bindable(),
|
||||
exists = $bindable()
|
||||
}: {
|
||||
pedimentoId: number | 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) {
|
||||
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: ''
|
||||
};
|
||||
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;
|
||||
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: ''
|
||||
};
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Card.Root>
|
||||
<Card.Header>
|
||||
<Card.Title>Fechas del Pedimento</Card.Title>
|
||||
<Card.Description>
|
||||
Gestiona las fechas importantes del pedimento
|
||||
</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">
|
||||
<!-- Fecha de Entrada -->
|
||||
<div class="space-y-2">
|
||||
<Label for="entry_date">Fecha de Entrada</Label>
|
||||
<Input
|
||||
id="entry_date"
|
||||
type="date"
|
||||
bind:value={formData.entry_date}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Fecha de Presentación -->
|
||||
<div class="space-y-2">
|
||||
<Label for="pedimento_date">Fecha de Presentación</Label>
|
||||
<Input
|
||||
id="pedimento_date"
|
||||
type="date"
|
||||
bind:value={formData.pedimento_date}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Fecha de Pago -->
|
||||
<div class="space-y-2">
|
||||
<Label for="payment_date">Fecha de Pago</Label>
|
||||
<Input
|
||||
id="payment_date"
|
||||
type="date"
|
||||
bind:value={formData.payment_date}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Fecha de Pago Rectificación -->
|
||||
<div class="space-y-2">
|
||||
<Label for="rectification_payment_date">Fecha de Pago Rectificación</Label>
|
||||
<Input
|
||||
id="rectification_payment_date"
|
||||
type="date"
|
||||
bind:value={formData.rectification_payment_date}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Fecha de Extracción -->
|
||||
<div class="space-y-2">
|
||||
<Label for="extraction_date">Fecha de Extracción</Label>
|
||||
<Input
|
||||
id="extraction_date"
|
||||
type="date"
|
||||
bind:value={formData.extraction_date}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Fecha de Presentación (Submission) -->
|
||||
<div class="space-y-2">
|
||||
<Label for="submission_date">Fecha de Envío</Label>
|
||||
<Input
|
||||
id="submission_date"
|
||||
type="date"
|
||||
bind:value={formData.submission_date}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Fecha EUCAN -->
|
||||
<div class="space-y-2">
|
||||
<Label for="eucan_date">Fecha EUCAN</Label>
|
||||
<Input
|
||||
id="eucan_date"
|
||||
type="date"
|
||||
bind:value={formData.eucan_date}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Fecha Original -->
|
||||
<div class="space-y-2">
|
||||
<Label for="original_date">Fecha Original</Label>
|
||||
<Input
|
||||
id="original_date"
|
||||
type="date"
|
||||
bind:value={formData.original_date}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Fecha de Inicio -->
|
||||
<div class="space-y-2">
|
||||
<Label for="start_date">Fecha de Inicio</Label>
|
||||
<Input
|
||||
id="start_date"
|
||||
type="date"
|
||||
bind:value={formData.start_date}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Fecha de Fin -->
|
||||
<div class="space-y-2">
|
||||
<Label for="end_date">Fecha de Fin</Label>
|
||||
<Input
|
||||
id="end_date"
|
||||
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>
|
||||
{/if}
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
@@ -0,0 +1,242 @@
|
||||
<script lang="ts">
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import type { Pedimento } from '$lib/api/dashboard/a76/pedimentos';
|
||||
|
||||
let {
|
||||
pedimento,
|
||||
formData = $bindable()
|
||||
}: {
|
||||
pedimento: Pedimento | null;
|
||||
formData?: any;
|
||||
} = $props();
|
||||
|
||||
// Inicializar formData con los valores del pedimento (o vacío si es null)
|
||||
if (!formData) {
|
||||
formData = {
|
||||
year: pedimento?.year || '',
|
||||
customs_office: pedimento?.customs_office || '',
|
||||
license: pedimento?.license || '',
|
||||
pedimento_number: pedimento?.pedimento_number || '',
|
||||
client_id: pedimento?.client_id ?? null,
|
||||
operation_type: pedimento?.operation_type ?? null,
|
||||
pedimento_type: pedimento?.pedimento_type ?? null,
|
||||
pedimento_code: pedimento?.pedimento_code || '',
|
||||
regime: pedimento?.regime || '',
|
||||
status: pedimento?.status || '',
|
||||
usd_value: pedimento?.usd_value ?? null,
|
||||
paid_price: pedimento?.paid_price ?? null,
|
||||
gross_weight: pedimento?.gross_weight ?? null,
|
||||
exchange_rate: pedimento?.exchange_rate ?? null
|
||||
};
|
||||
}
|
||||
|
||||
const statusOptions = [
|
||||
{ value: 'MODIFICABLE', label: 'Modificable' },
|
||||
{ value: 'ESPERA FIRMA PREVIO', label: 'Espera de firma previo' },
|
||||
{ value: 'ESPERA VALIDACION', label: 'Espera validacion' },
|
||||
{ value: 'VALIDADO', label: 'Validado' },
|
||||
{ value: 'ESPERA BORRAR FIRMA PREVIO', label: 'Espera borrar firma previo' },
|
||||
{ value: 'ESPERA BORRAR FIRMA VALIDACION', label: 'Espera borrar firma validacion ' },
|
||||
{ value: 'ESPERA PAGO', label: 'Espera pago' },
|
||||
{ value: 'CON FIRMA DE PREVIO', label: 'Con firma de previo' },
|
||||
{ value: 'F DE PREVIO BORRADA', label: 'Forma de previo borrada' },
|
||||
{ value: 'F VALIDACION BORRADA', label: 'Forma validacion borrada' },
|
||||
{ value: 'PAGADO', label: 'Pagado' },
|
||||
{ value: 'DESISTIO', label: 'Desistio' },
|
||||
{ value: 'ESPERA CARTA CUPO', label: 'Espera carta cupo' },
|
||||
{ value: 'CARTA CUPO', label: 'Carta cupo' },
|
||||
{ value: 'ESPERA CANCELAR CARTA CUPO', label: 'Espera cancelar carta cupo' }
|
||||
];
|
||||
</script>
|
||||
|
||||
<Card.Root>
|
||||
<Card.Header>
|
||||
<Card.Title>Información General</Card.Title>
|
||||
<Card.Description>
|
||||
Edita los datos principales del pedimento
|
||||
</Card.Description>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<div class="space-y-6">
|
||||
<!-- Fila 1: Año (2), Aduana (2), Patente (4), Número de Pedimento (7) -->
|
||||
<div class="flex items-end gap-2">
|
||||
<!-- Año -->
|
||||
<div class="space-y-2 w-16">
|
||||
<Label for="year">Año</Label>
|
||||
<Input
|
||||
id="year"
|
||||
bind:value={formData.year}
|
||||
placeholder="23"
|
||||
maxlength={2}
|
||||
class="text-center"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Separador -->
|
||||
<div class="pb-2 text-2xl font-semibold text-muted-foreground">-</div>
|
||||
|
||||
<!-- Aduana -->
|
||||
<div class="space-y-2 w-16">
|
||||
<Label for="customs_office">Aduana</Label>
|
||||
<Input
|
||||
id="customs_office"
|
||||
bind:value={formData.customs_office}
|
||||
placeholder="01"
|
||||
maxlength={2}
|
||||
class="text-center"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Separador -->
|
||||
<div class="pb-2 text-2xl font-semibold text-muted-foreground">-</div>
|
||||
|
||||
<!-- 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"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Separador -->
|
||||
<div class="pb-2 text-2xl font-semibold text-muted-foreground">-</div>
|
||||
|
||||
<!-- Número de Pedimento -->
|
||||
<div class="space-y-2 flex-1">
|
||||
<Label for="pedimento_number">Número de Pedimento</Label>
|
||||
<Input
|
||||
id="pedimento_number"
|
||||
bind:value={formData.pedimento_number}
|
||||
placeholder="0000001"
|
||||
maxlength={7}
|
||||
class="text-left"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Tipo de Pedimento -->
|
||||
<div class="space-y-2">
|
||||
<Label for="pedimento_type">Tipo de Pedimento</Label>
|
||||
<Input
|
||||
id="pedimento_type"
|
||||
type="number"
|
||||
bind:value={formData.pedimento_type}
|
||||
placeholder="Ej: 1"
|
||||
/>
|
||||
</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>
|
||||
|
||||
<!-- Régimen -->
|
||||
<div class="space-y-2">
|
||||
<Label for="regime">Régimen</Label>
|
||||
<Input
|
||||
id="regime"
|
||||
bind:value={formData.regime}
|
||||
placeholder="Ej: IMD"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 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"
|
||||
>
|
||||
<option value="">Seleccionar...</option>
|
||||
{#each statusOptions as option}
|
||||
<option value={option.value}>{option.label}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Valor USD -->
|
||||
<div class="space-y-2">
|
||||
<Label for="usd_value">Valor USD</Label>
|
||||
<Input
|
||||
id="usd_value"
|
||||
type="number"
|
||||
step="0.01"
|
||||
bind:value={formData.usd_value}
|
||||
placeholder="Ej: 1000.00"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Precio Pagado -->
|
||||
<div class="space-y-2">
|
||||
<Label for="paid_price">Precio Pagado</Label>
|
||||
<Input
|
||||
id="paid_price"
|
||||
type="number"
|
||||
step="0.01"
|
||||
bind:value={formData.paid_price}
|
||||
placeholder="Ej: 1000.00"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Peso Bruto -->
|
||||
<div class="space-y-2">
|
||||
<Label for="gross_weight">Peso Bruto (Kg)</Label>
|
||||
<Input
|
||||
id="gross_weight"
|
||||
type="number"
|
||||
step="0.01"
|
||||
bind:value={formData.gross_weight}
|
||||
placeholder="Ej: 100.00"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Tipo de Cambio -->
|
||||
<div class="space-y-2">
|
||||
<Label for="exchange_rate">Tipo de Cambio</Label>
|
||||
<Input
|
||||
id="exchange_rate"
|
||||
type="number"
|
||||
step="0.0001"
|
||||
bind:value={formData.exchange_rate}
|
||||
placeholder="Ej: 17.5000"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
@@ -0,0 +1,257 @@
|
||||
<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';
|
||||
|
||||
let {
|
||||
pedimentoId,
|
||||
formData = $bindable(),
|
||||
exists = $bindable()
|
||||
}: {
|
||||
pedimentoId: number | 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) {
|
||||
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
|
||||
};
|
||||
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;
|
||||
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
|
||||
};
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Card.Root>
|
||||
<Card.Header>
|
||||
<Card.Title>Información de Pagos</Card.Title>
|
||||
<Card.Description>
|
||||
Gestiona la información de pagos del pedimento
|
||||
</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>
|
||||
</div>
|
||||
{/if}
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
@@ -0,0 +1,141 @@
|
||||
<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';
|
||||
|
||||
let {
|
||||
pedimentoId,
|
||||
formData = $bindable(),
|
||||
exists = $bindable()
|
||||
}: {
|
||||
pedimentoId: number | 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) {
|
||||
formData = {
|
||||
destination: null,
|
||||
entry_exit: '',
|
||||
arrival: '',
|
||||
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;
|
||||
formData = {
|
||||
destination: null,
|
||||
entry_exit: '',
|
||||
arrival: '',
|
||||
departure: ''
|
||||
};
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Card.Root>
|
||||
<Card.Header>
|
||||
<Card.Title>Medios de Transporte</Card.Title>
|
||||
<Card.Description>
|
||||
Gestiona la información de transporte del pedimento
|
||||
</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>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<!-- 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>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
@@ -0,0 +1,207 @@
|
||||
<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';
|
||||
|
||||
let {
|
||||
pedimentoId,
|
||||
formData = $bindable(),
|
||||
exists = $bindable()
|
||||
}: {
|
||||
pedimentoId: number | 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) {
|
||||
formData = {
|
||||
validator: '',
|
||||
validation_ack: '',
|
||||
pre_ack: '',
|
||||
line_signature: '',
|
||||
electronic_signature: '',
|
||||
certificate_number: '',
|
||||
validator_id: null,
|
||||
responsible_id: null
|
||||
};
|
||||
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;
|
||||
formData = {
|
||||
validator: '',
|
||||
validation_ack: '',
|
||||
pre_ack: '',
|
||||
line_signature: '',
|
||||
electronic_signature: '',
|
||||
certificate_number: '',
|
||||
validator_id: null,
|
||||
responsible_id: null
|
||||
};
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Card.Root>
|
||||
<Card.Header>
|
||||
<Card.Title>Validación del Pedimento</Card.Title>
|
||||
<Card.Description>
|
||||
Gestiona la documentación de validación del pedimento
|
||||
</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>
|
||||
|
||||
<!-- Firma de Línea -->
|
||||
<div class="space-y-2">
|
||||
<Label for="line_signature">Firma de Línea</Label>
|
||||
<Input
|
||||
id="line_signature"
|
||||
bind:value={formData.line_signature}
|
||||
placeholder="Máx. 50 caracteres"
|
||||
maxlength={50}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Firma Electrónica -->
|
||||
<div class="space-y-2">
|
||||
<Label for="electronic_signature">Firma Electrónica</Label>
|
||||
<Textarea
|
||||
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}
|
||||
/>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
Firma electrónica del pedimento (máximo 999 caracteres).
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
@@ -26,7 +26,7 @@
|
||||
<div
|
||||
class="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg"
|
||||
>
|
||||
<activeTeam.logo class="size-4" />
|
||||
<activeTeam.logo class="size-4 text-white" />
|
||||
</div>
|
||||
<div class="grid flex-1 text-left text-sm leading-tight">
|
||||
<span class="truncate font-medium">
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="alert-description"
|
||||
class={cn(
|
||||
"text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
20
frontend/src/lib/components/ui/alert/alert-title.svelte
Normal file
20
frontend/src/lib/components/ui/alert/alert-title.svelte
Normal file
@@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="alert-title"
|
||||
class={cn("col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
44
frontend/src/lib/components/ui/alert/alert.svelte
Normal file
44
frontend/src/lib/components/ui/alert/alert.svelte
Normal file
@@ -0,0 +1,44 @@
|
||||
<script lang="ts" module>
|
||||
import { type VariantProps, tv } from "tailwind-variants";
|
||||
|
||||
export const alertVariants = tv({
|
||||
base: "relative grid w-full grid-cols-[0_1fr] items-start gap-y-0.5 rounded-lg border px-4 py-3 text-sm has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] has-[>svg]:gap-x-3 [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-card text-card-foreground",
|
||||
destructive:
|
||||
"text-destructive bg-card *:data-[slot=alert-description]:text-destructive/90 [&>svg]:text-current",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
});
|
||||
|
||||
export type AlertVariant = VariantProps<typeof alertVariants>["variant"];
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
variant = "default",
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
|
||||
variant?: AlertVariant;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="alert"
|
||||
class={cn(alertVariants({ variant }), className)}
|
||||
{...restProps}
|
||||
role="alert"
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
14
frontend/src/lib/components/ui/alert/index.ts
Normal file
14
frontend/src/lib/components/ui/alert/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import Root from "./alert.svelte";
|
||||
import Description from "./alert-description.svelte";
|
||||
import Title from "./alert-title.svelte";
|
||||
export { alertVariants, type AlertVariant } from "./alert.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
Description,
|
||||
Title,
|
||||
//
|
||||
Root as Alert,
|
||||
Description as AlertDescription,
|
||||
Title as AlertTitle,
|
||||
};
|
||||
16
frontend/src/lib/components/ui/tabs/index.ts
Normal file
16
frontend/src/lib/components/ui/tabs/index.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import Root from "./tabs.svelte";
|
||||
import Content from "./tabs-content.svelte";
|
||||
import List from "./tabs-list.svelte";
|
||||
import Trigger from "./tabs-trigger.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
Content,
|
||||
List,
|
||||
Trigger,
|
||||
//
|
||||
Root as Tabs,
|
||||
Content as TabsContent,
|
||||
List as TabsList,
|
||||
Trigger as TabsTrigger,
|
||||
};
|
||||
17
frontend/src/lib/components/ui/tabs/tabs-content.svelte
Normal file
17
frontend/src/lib/components/ui/tabs/tabs-content.svelte
Normal file
@@ -0,0 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { Tabs as TabsPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: TabsPrimitive.ContentProps = $props();
|
||||
</script>
|
||||
|
||||
<TabsPrimitive.Content
|
||||
bind:ref
|
||||
data-slot="tabs-content"
|
||||
class={cn("flex-1 outline-none", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
20
frontend/src/lib/components/ui/tabs/tabs-list.svelte
Normal file
20
frontend/src/lib/components/ui/tabs/tabs-list.svelte
Normal file
@@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { Tabs as TabsPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: TabsPrimitive.ListProps = $props();
|
||||
</script>
|
||||
|
||||
<TabsPrimitive.List
|
||||
bind:ref
|
||||
data-slot="tabs-list"
|
||||
class={cn(
|
||||
"bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
20
frontend/src/lib/components/ui/tabs/tabs-trigger.svelte
Normal file
20
frontend/src/lib/components/ui/tabs/tabs-trigger.svelte
Normal file
@@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { Tabs as TabsPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: TabsPrimitive.TriggerProps = $props();
|
||||
</script>
|
||||
|
||||
<TabsPrimitive.Trigger
|
||||
bind:ref
|
||||
data-slot="tabs-trigger"
|
||||
class={cn(
|
||||
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 whitespace-nowrap rounded-md border border-transparent px-2 py-1 text-sm font-medium transition-[color,box-shadow] focus-visible:outline-1 focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
19
frontend/src/lib/components/ui/tabs/tabs.svelte
Normal file
19
frontend/src/lib/components/ui/tabs/tabs.svelte
Normal file
@@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { Tabs as TabsPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
value = $bindable(""),
|
||||
class: className,
|
||||
...restProps
|
||||
}: TabsPrimitive.RootProps = $props();
|
||||
</script>
|
||||
|
||||
<TabsPrimitive.Root
|
||||
bind:ref
|
||||
bind:value
|
||||
data-slot="tabs"
|
||||
class={cn("flex flex-col gap-2", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -1,12 +1,45 @@
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ cookies }) => {
|
||||
export const load: PageServerLoad = async ({ cookies, fetch }) => {
|
||||
const token = cookies.get('access_token');
|
||||
|
||||
// Si está autenticado, redirigir al dashboard
|
||||
// Si hay token, validar que sea válido antes de redirigir
|
||||
if (token) {
|
||||
throw redirect(303, '/dashboard');
|
||||
try {
|
||||
// Configurar la URL de la API
|
||||
let apiUrl = process.env.INTERNAL_API_URL;
|
||||
if (!apiUrl) {
|
||||
apiUrl = import.meta.env.VITE_API_URL;
|
||||
apiUrl = apiUrl?.replace('localhost', 'backend').replace('127.0.0.1', 'backend');
|
||||
}
|
||||
|
||||
const baseUrl = apiUrl?.endsWith('/') ? apiUrl : `${apiUrl}/`;
|
||||
|
||||
// Verificar si el token es válido
|
||||
const response = await fetch(`${baseUrl}v1/auth/me`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
|
||||
// Solo redirigir al dashboard si el token es válido
|
||||
if (response.ok) {
|
||||
throw redirect(303, '/dashboard');
|
||||
} else {
|
||||
// Token inválido, limpiar cookies y mostrar la página pública
|
||||
cookies.delete('access_token', { path: '/' });
|
||||
cookies.delete('refresh_token', { path: '/' });
|
||||
}
|
||||
} catch (error) {
|
||||
// Si es un redirect, re-lanzarlo
|
||||
if (error && typeof error === 'object' && 'status' in error && 'location' in error) {
|
||||
throw error;
|
||||
}
|
||||
// Para otros errores, limpiar cookies y continuar
|
||||
cookies.delete('access_token', { path: '/' });
|
||||
cookies.delete('refresh_token', { path: '/' });
|
||||
}
|
||||
}
|
||||
|
||||
// Si no está autenticado, mostrar la página principal pública
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
Bienvenido a Anexo76
|
||||
</h2>
|
||||
<p class="mt-6 text-lg leading-8 text-gray-600">
|
||||
Plataforma SaaS para gestión de comercio exterior conforme a Anexos 24, 31 y 22 del SAT.
|
||||
Plataforma SaaS para gestión de comercio exterior conforme a Anexos 24, 30 y 22 del SAT.
|
||||
Ideal para maquilas, empresas IMMEX y agentes aduanales.
|
||||
</p>
|
||||
<div class="mt-10 flex items-center justify-center gap-x-6">
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
<div class="flex items-center gap-2 px-4">
|
||||
<Sidebar.Trigger class="-ml-1" />
|
||||
<Separator orientation="vertical" class="mr-2 data-[orientation=vertical]:h-4" />
|
||||
<!--
|
||||
<Breadcrumb.Root>
|
||||
<Breadcrumb.List>
|
||||
<Breadcrumb.Item class="hidden md:block">
|
||||
@@ -32,6 +33,7 @@
|
||||
</Breadcrumb.Item>
|
||||
</Breadcrumb.List>
|
||||
</Breadcrumb.Root>
|
||||
-->
|
||||
</div>
|
||||
</header>
|
||||
<div class="flex flex-1 flex-col gap-4 p-4 pt-0">
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<div class="flex flex-col gap-2">
|
||||
<h1 class="text-3xl font-bold tracking-tight">Bienvenido al Dashboard</h1>
|
||||
<p class="text-muted-foreground">
|
||||
Sistema de gestión de comercio exterior conforme a Anexos 24, 31 y 22 del SAT
|
||||
Sistema de gestión de comercio exterior conforme a Anexos 24, 30 y 22 del SAT
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { pedimentosApi, type Pedimento } from '$lib/api/dashboard/a76/pedimentos';
|
||||
import DataTable from '$lib/components/dashboard/pedimentos/data-table.svelte';
|
||||
import { createColumns } from '$lib/components/dashboard/pedimentos/columns.js';
|
||||
import CreateEditDialog from '$lib/components/dashboard/pedimentos/create-edit-dialog.svelte';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
@@ -13,9 +12,6 @@
|
||||
|
||||
// Los datos iniciales vienen del servidor
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
// Estado para el diálogo de crear
|
||||
let showCreateDialog = $state(false);
|
||||
|
||||
// Estado para filtros
|
||||
let filters = $state({
|
||||
@@ -163,7 +159,8 @@
|
||||
}
|
||||
|
||||
function handleCreateClick() {
|
||||
showCreateDialog = true;
|
||||
// Redirigir a la página de creación (reusa la página de edición con ID "new")
|
||||
window.location.href = '/dashboard/pedimentos/edit/new';
|
||||
}
|
||||
|
||||
function handleSuccess() {
|
||||
@@ -174,11 +171,21 @@
|
||||
// Opciones de status para el filtro
|
||||
const statusOptions = [
|
||||
{ value: "", label: "Todos" },
|
||||
{ value: "Activo", label: "Activo" },
|
||||
{ value: "Pendiente", label: "Pendiente" },
|
||||
{ value: "En Proceso", label: "En Proceso" },
|
||||
{ value: "Completado", label: "Completado" },
|
||||
{ value: "Cancelado", label: "Cancelado" }
|
||||
{ value: 'MODIFICABLE', label: 'Modificable' },
|
||||
{ value: 'ESPERA FIRMA PREVIO', label: 'Espera de firma previo' },
|
||||
{ value: 'ESPERA VALIDACION', label: 'Espera validacion' },
|
||||
{ value: 'VALIDADO', label: 'Validado' },
|
||||
{ value: 'ESPERA BORRAR FIRMA PREVIO', label: 'Espera borrar firma previo' },
|
||||
{ value: 'ESPERA BORRAR FIRMA VALIDACION', label: 'Espera borrar firma validacion ' },
|
||||
{ value: 'ESPERA PAGO', label: 'Espera pago' },
|
||||
{ value: 'CON FIRMA DE PREVIO', label: 'Con firma de previo' },
|
||||
{ value: 'F DE PREVIO BORRADA', label: 'Forma de previo borrada' },
|
||||
{ value: 'F VALIDACION BORRADA', label: 'Forma validacion borrada' },
|
||||
{ value: 'PAGADO', label: 'Pagado' },
|
||||
{ value: 'DESISTIO', label: 'Desistio' },
|
||||
{ value: 'ESPERA CARTA CUPO', label: 'Espera carta cupo' },
|
||||
{ value: 'CARTA CUPO', label: 'Carta cupo' },
|
||||
{ value: 'ESPERA CANCELAR CARTA CUPO', label: 'Espera cancelar carta cupo' }
|
||||
];
|
||||
|
||||
// Crear columnas con el callback onSuccess
|
||||
@@ -347,6 +354,3 @@
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
</div>
|
||||
|
||||
<!-- Diálogo de crear/editar -->
|
||||
<CreateEditDialog bind:open={showCreateDialog} onSuccess={handleSuccess} />
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { error, redirect } from '@sveltejs/kit';
|
||||
|
||||
export const load: PageServerLoad = async ({ params, cookies }) => {
|
||||
const accessToken = cookies.get('access_token');
|
||||
|
||||
if (!accessToken) {
|
||||
throw redirect(302, '/login');
|
||||
}
|
||||
|
||||
// Si el ID es "new", es una creación
|
||||
if (params.id === 'new') {
|
||||
return {
|
||||
pedimento: null,
|
||||
pedimentoId: null,
|
||||
isCreate: true
|
||||
};
|
||||
}
|
||||
|
||||
const pedimentoId = parseInt(params.id);
|
||||
if (isNaN(pedimentoId)) {
|
||||
throw error(400, 'ID de pedimento inválido');
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
// Configurar la URL de la API para SSR
|
||||
let apiUrl = process.env.INTERNAL_API_URL;
|
||||
if (!apiUrl) {
|
||||
apiUrl = import.meta.env.VITE_API_URL;
|
||||
// Reemplazar 'localhost' con 'backend' para llamadas desde el servidor (SSR)
|
||||
apiUrl = apiUrl?.replace('localhost', 'backend').replace('127.0.0.1', 'backend');
|
||||
}
|
||||
|
||||
// Normalizar la URL
|
||||
const baseUrl = apiUrl?.endsWith('/') ? apiUrl : `${apiUrl}/`;
|
||||
// Cargar el pedimento desde el backend
|
||||
const response = await fetch(
|
||||
`${baseUrl}v1/a76/pedimentos/${pedimentoId}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 404) {
|
||||
throw error(404, 'Pedimento no encontrado');
|
||||
}
|
||||
if (response.status === 401) {
|
||||
throw redirect(302, '/login');
|
||||
}
|
||||
throw error(response.status, 'Error al cargar el pedimento');
|
||||
}
|
||||
|
||||
const pedimento = await response.json();
|
||||
|
||||
return {
|
||||
pedimento,
|
||||
pedimentoId,
|
||||
isCreate: false
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('Error loading pedimento:', e);
|
||||
if (e instanceof Error && 'status' in e) {
|
||||
throw e;
|
||||
}
|
||||
throw error(500, 'Error al cargar el pedimento');
|
||||
}
|
||||
};
|
||||
488
frontend/src/routes/dashboard/pedimentos/edit/[id]/+page.svelte
Normal file
488
frontend/src/routes/dashboard/pedimentos/edit/[id]/+page.svelte
Normal file
@@ -0,0 +1,488 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import * as Tabs from '$lib/components/ui/tabs';
|
||||
import * as Alert from '$lib/components/ui/alert';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Badge } from '$lib/components/ui/badge';
|
||||
import { Separator } from '$lib/components/ui/separator';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
// Importar los componentes de cada pestaña (ahora sin botones de guardar propios)
|
||||
import GeneralTabForm from '$lib/components/dashboard/pedimentos/edit/general-tab-form.svelte';
|
||||
import DatesTabForm from '$lib/components/dashboard/pedimentos/edit/dates-tab-form.svelte';
|
||||
import PaymentsTabForm from '$lib/components/dashboard/pedimentos/edit/payments-tab-form.svelte';
|
||||
import TransportTabForm from '$lib/components/dashboard/pedimentos/edit/transport-tab-form.svelte';
|
||||
import ValidationTabForm from '$lib/components/dashboard/pedimentos/edit/validation-tab-form.svelte';
|
||||
|
||||
// Importar las APIs
|
||||
import { pedimentosApi, type CreatePedimentoData, type UpdatePedimentoData } from '$lib/api/dashboard/a76/pedimentos';
|
||||
import { pedimentoDatesApi, type UpdatePedimentoDatesData, type CreatePedimentoDatesData } from '$lib/api/dashboard/a76/pedimento-dates';
|
||||
import { pedimentoPaymentsApi, type UpdatePedimentoPaymentsData, type CreatePedimentoPaymentsData } from '$lib/api/dashboard/a76/pedimento-payments';
|
||||
import { pedimentoTransportApi, type UpdatePedimentoTransportMeansData, type CreatePedimentoTransportMeansData } from '$lib/api/dashboard/a76/pedimento-transport';
|
||||
import { pedimentoValidationApi, type UpdatePedimentoValidationData, type CreatePedimentoValidationData } from '$lib/api/dashboard/a76/pedimento-validation';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
let activeTab = $state('general');
|
||||
let saving = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
let success = $state(false);
|
||||
|
||||
// ID del pedimento - se usa para crear sub-recursos después de crear el pedimento
|
||||
let pedimentoId = $state<number | null>(data.pedimentoId);
|
||||
|
||||
// Referencias a los componentes de formulario para obtener sus datos
|
||||
let generalFormData = $state<any>(null);
|
||||
let datesFormData = $state<any>(null);
|
||||
let paymentsFormData = $state<any>(null);
|
||||
let transportFormData = $state<any>(null);
|
||||
let validationFormData = $state<any>(null);
|
||||
|
||||
// Estados para saber si existen datos previos
|
||||
let datesExists = $state(false);
|
||||
let paymentsExists = $state(false);
|
||||
let transportExists = $state(false);
|
||||
let validationExists = $state(false);
|
||||
|
||||
function handleBack() {
|
||||
goto('/dashboard/pedimentos');
|
||||
}
|
||||
|
||||
function getStatusColor(status?: string | null): 'default' | 'destructive' | 'outline' | 'secondary' {
|
||||
if (!status) return 'secondary';
|
||||
|
||||
const statusLower = status.toLowerCase();
|
||||
if (statusLower.includes('activo') || statusLower.includes('completado')) {
|
||||
return 'default';
|
||||
} else if (statusLower.includes('pendiente') || statusLower.includes('proceso')) {
|
||||
return 'secondary';
|
||||
} else if (statusLower.includes('cancelado')) {
|
||||
return 'destructive';
|
||||
}
|
||||
return 'outline';
|
||||
}
|
||||
|
||||
async function handleSaveAll() {
|
||||
saving = true;
|
||||
error = null;
|
||||
success = false;
|
||||
|
||||
try {
|
||||
let pedimentoId = data.pedimentoId;
|
||||
|
||||
// 1. Crear o actualizar datos generales
|
||||
if (generalFormData) {
|
||||
const payload = {
|
||||
year: generalFormData.year || null,
|
||||
customs_office: generalFormData.customs_office || null,
|
||||
license: generalFormData.license || null,
|
||||
pedimento_number: generalFormData.pedimento_number || null,
|
||||
client_id: generalFormData.client_id,
|
||||
operation_type: generalFormData.operation_type,
|
||||
pedimento_type: generalFormData.pedimento_type,
|
||||
pedimento_code: generalFormData.pedimento_code || null,
|
||||
regime: generalFormData.regime || null,
|
||||
status: generalFormData.status || null,
|
||||
usd_value: generalFormData.usd_value,
|
||||
paid_price: generalFormData.paid_price,
|
||||
gross_weight: generalFormData.gross_weight,
|
||||
exchange_rate: generalFormData.exchange_rate
|
||||
};
|
||||
|
||||
if (data.isCreate) {
|
||||
// Crear nuevo pedimento
|
||||
const response = await pedimentosApi.create(payload);
|
||||
if (response.error) throw new Error(response.error);
|
||||
if (!response.data?.id) throw new Error('No se recibió el ID del pedimento creado');
|
||||
pedimentoId = response.data.id;
|
||||
} else {
|
||||
// Actualizar pedimento existente
|
||||
const response = await pedimentosApi.update(pedimentoId!, payload);
|
||||
if (response.error) throw new Error(response.error);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Guardar fechas
|
||||
if (datesFormData && (datesFormData.entry_date || datesFormData.pedimento_date || datesFormData.payment_date)) {
|
||||
const payload = {
|
||||
entry_date: datesFormData.entry_date || null,
|
||||
pedimento_date: datesFormData.pedimento_date || null,
|
||||
payment_date: datesFormData.payment_date || null
|
||||
};
|
||||
|
||||
if (datesExists && !data.isCreate) {
|
||||
const response = await pedimentoDatesApi.update(pedimentoId!, payload as UpdatePedimentoDatesData);
|
||||
if (response.error && response.status !== 404) throw new Error(response.error);
|
||||
} else {
|
||||
const response = await pedimentoDatesApi.create(pedimentoId!, payload as CreatePedimentoDatesData);
|
||||
if (response.error) throw new Error(response.error);
|
||||
datesExists = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Guardar pagos
|
||||
if (paymentsFormData && (paymentsFormData.payment_form || paymentsFormData.bank_identifier)) {
|
||||
const payload = {
|
||||
payment_form: paymentsFormData.payment_form || null,
|
||||
bank_identifier: paymentsFormData.bank_identifier || null
|
||||
};
|
||||
|
||||
if (paymentsExists && !data.isCreate) {
|
||||
const response = await pedimentoPaymentsApi.update(pedimentoId!, payload as UpdatePedimentoPaymentsData);
|
||||
if (response.error && response.status !== 404) throw new Error(response.error);
|
||||
} else {
|
||||
const response = await pedimentoPaymentsApi.create(pedimentoId!, payload as CreatePedimentoPaymentsData);
|
||||
if (response.error) throw new Error(response.error);
|
||||
paymentsExists = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Guardar transporte
|
||||
if (transportFormData && (transportFormData.arrival_key || transportFormData.arrival_data || transportFormData.departure_key || transportFormData.departure_data)) {
|
||||
const payload = {
|
||||
arrival_key: transportFormData.arrival_key || null,
|
||||
arrival_data: transportFormData.arrival_data || null,
|
||||
departure_key: transportFormData.departure_key || null,
|
||||
departure_data: transportFormData.departure_data || null
|
||||
};
|
||||
|
||||
if (transportExists && !data.isCreate) {
|
||||
const response = await pedimentoTransportApi.update(pedimentoId!, payload as UpdatePedimentoTransportMeansData);
|
||||
if (response.error && response.status !== 404) throw new Error(response.error);
|
||||
} else {
|
||||
const response = await pedimentoTransportApi.create(pedimentoId!, payload as CreatePedimentoTransportMeansData);
|
||||
if (response.error) throw new Error(response.error);
|
||||
transportExists = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Guardar validación
|
||||
if (validationFormData && validationFormData.document) {
|
||||
const payload = {
|
||||
document: validationFormData.document || null
|
||||
};
|
||||
|
||||
if (validationExists && !data.isCreate) {
|
||||
const response = await pedimentoValidationApi.update(pedimentoId!, payload as UpdatePedimentoValidationData);
|
||||
if (response.error && response.status !== 404) throw new Error(response.error);
|
||||
} else {
|
||||
const response = await pedimentoValidationApi.create(pedimentoId!, payload as CreatePedimentoValidationData);
|
||||
if (response.error) throw new Error(response.error);
|
||||
validationExists = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Si fue una creación, redirigir a la página de edición
|
||||
if (data.isCreate && pedimentoId) {
|
||||
await goto(`/dashboard/pedimentos/edit/${pedimentoId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
success = true;
|
||||
setTimeout(() => {
|
||||
success = false;
|
||||
}, 3000);
|
||||
} catch (e) {
|
||||
if (e instanceof Error && e.message.includes('401')) {
|
||||
error = 'Sesión expirada. Recargando página...';
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1500);
|
||||
} else {
|
||||
error = e instanceof Error ? e.message : 'Error al guardar los cambios';
|
||||
}
|
||||
console.error('Error saving all:', e);
|
||||
} finally {
|
||||
saving = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="space-y-6">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between">
|
||||
<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>
|
||||
</Button>
|
||||
<h1 class="text-3xl font-bold tracking-tight">
|
||||
{#if data.isCreate}
|
||||
Nuevo Pedimento
|
||||
{:else}
|
||||
Pedimento #{data.pedimento.id}
|
||||
{/if}
|
||||
</h1>
|
||||
{#if data.isCreate}
|
||||
<Badge variant="default">Nuevo</Badge>
|
||||
{:else}
|
||||
<Badge variant={getStatusColor(data.pedimento.status)}>
|
||||
{data.pedimento.status || 'Sin estado'}
|
||||
</Badge>
|
||||
{/if}
|
||||
</div>
|
||||
<p class="text-muted-foreground">
|
||||
{#if !data.isCreate && data.pedimento.pedimento_number}
|
||||
Número: {data.pedimento.year}-{data.pedimento.customs_office}-{data.pedimento.license}-{data.pedimento.pedimento_number}
|
||||
{:else}
|
||||
Edita los detalles del pedimento
|
||||
{/if}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<!-- 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>
|
||||
<Alert.Title>Error</Alert.Title>
|
||||
<Alert.Description>{error}</Alert.Description>
|
||||
</Alert.Root>
|
||||
{/if}
|
||||
|
||||
{#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>
|
||||
<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>
|
||||
|
||||
<Tabs.Content value="general">
|
||||
<GeneralTabForm
|
||||
pedimento={data.pedimento}
|
||||
bind:formData={generalFormData}
|
||||
/>
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.Content value="dates">
|
||||
<DatesTabForm
|
||||
pedimentoId={pedimentoId}
|
||||
bind:formData={datesFormData}
|
||||
bind:exists={datesExists}
|
||||
/>
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.Content value="payments">
|
||||
<PaymentsTabForm
|
||||
pedimentoId={pedimentoId}
|
||||
bind:formData={paymentsFormData}
|
||||
bind:exists={paymentsExists}
|
||||
/>
|
||||
</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>
|
||||
</div>
|
||||
@@ -2,20 +2,15 @@ import { redirect, fail } from '@sveltejs/kit';
|
||||
import type { Actions, PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ cookies, url }) => {
|
||||
const token = cookies.get('access_token');
|
||||
|
||||
// Si hay un parámetro 'logout' en la URL, limpiar las cookies
|
||||
if (url.searchParams.has('logout')) {
|
||||
cookies.delete('access_token', { path: '/' });
|
||||
cookies.delete('refresh_token', { path: '/' });
|
||||
return {};
|
||||
}
|
||||
|
||||
// Si está autenticado, redirigir al dashboard
|
||||
if (token) {
|
||||
throw redirect(303, '/dashboard');
|
||||
}
|
||||
|
||||
// Si no está autenticado, permitir acceso al login
|
||||
// Permitir acceso al login sin redirigir automáticamente
|
||||
// Esto evita bucles de redirección cuando el token existe pero puede estar expirado
|
||||
return {};
|
||||
};
|
||||
|
||||
|
||||
@@ -21,7 +21,8 @@ psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-E
|
||||
CREATE SCHEMA IF NOT EXISTS a76;
|
||||
CREATE SCHEMA IF NOT EXISTS a22;
|
||||
CREATE SCHEMA IF NOT EXISTS a24;
|
||||
CREATE SCHEMA IF NOT EXISTS a31;
|
||||
CREATE SCHEMA IF NOT EXISTS a30;
|
||||
DROP SCHEMA IF EXISTS a31;
|
||||
EOSQL
|
||||
|
||||
echo "✓ Extensiones y esquemas creados correctamente"
|
||||
|
||||
Reference in New Issue
Block a user