feat: update company API endpoint, refine pedimento dates model, and adjust Keycloak mapper creation

This commit is contained in:
2025-11-24 09:58:24 -06:00
parent 3043be4624
commit cdc3788a05
7 changed files with 114 additions and 102 deletions

View File

@@ -7,9 +7,8 @@ from pydantic import BaseModel, ConfigDict, Field
class PedimentoDatesBase(BaseModel): class PedimentoDatesBase(BaseModel):
"""Base schema for Pedimento Dates""" """Base schema for Pedimento Dates"""
entry_date: Optional[datetime] = Field(None, description="Entry date") entry_date: Optional[datetime] = Field(None, description="Entry date")
pedimento_date: Optional[datetime] = Field(None, description="Pedimento date") payment_date: datetime = Field(None, description="Payment date")
payment_date: Optional[datetime] = Field(None, description="Payment date")
rectification_payment_date: Optional[datetime] = Field( rectification_payment_date: Optional[datetime] = Field(
None, description="Rectification payment date" None, description="Rectification payment date"
) )
@@ -19,16 +18,13 @@ class PedimentoDatesBase(BaseModel):
original_date: Optional[datetime] = Field(None, description="Original date") original_date: Optional[datetime] = Field(None, description="Original date")
start_date: Optional[datetime] = Field(None, description="Start date") start_date: Optional[datetime] = Field(None, description="Start date")
end_date: Optional[datetime] = Field(None, description="End date") end_date: Optional[datetime] = Field(None, description="End date")
capture_date: Optional[datetime] = Field(None, description="Capture date")
capture_time: Optional[time] = Field(None, description="Capture time")
class PedimentoDatesCreate(BaseModel): class PedimentoDatesCreate(BaseModel):
"""Schema for creating a new Pedimento Dates - pedimento_id and tenant_id are set by backend""" """Schema for creating a new Pedimento Dates - pedimento_id and tenant_id are set by backend"""
entry_date: Optional[datetime] = Field(None, description="Entry date") entry_date: Optional[datetime] = Field(None, description="Entry date")
pedimento_date: Optional[datetime] = Field(None, description="Pedimento date") payment_date: datetime = Field(..., description="Payment date")
payment_date: Optional[datetime] = Field(None, description="Payment date")
rectification_payment_date: Optional[datetime] = Field(None, description="Rectification payment date") rectification_payment_date: Optional[datetime] = Field(None, description="Rectification payment date")
extraction_date: Optional[datetime] = Field(None, description="Extraction date") extraction_date: Optional[datetime] = Field(None, description="Extraction date")
submission_date: Optional[datetime] = Field(None, description="Submission date") submission_date: Optional[datetime] = Field(None, description="Submission date")
@@ -36,15 +32,12 @@ class PedimentoDatesCreate(BaseModel):
original_date: Optional[datetime] = Field(None, description="Original date") original_date: Optional[datetime] = Field(None, description="Original date")
start_date: Optional[datetime] = Field(None, description="Start date") start_date: Optional[datetime] = Field(None, description="Start date")
end_date: Optional[datetime] = Field(None, description="End date") end_date: Optional[datetime] = Field(None, description="End date")
capture_date: Optional[datetime] = Field(None, description="Capture date")
capture_time: Optional[time] = Field(None, description="Capture time")
class PedimentoDatesUpdate(BaseModel): class PedimentoDatesUpdate(BaseModel):
"""Schema for updating a Pedimento Dates""" """Schema for updating a Pedimento Dates"""
entry_date: Optional[datetime] = None entry_date: Optional[datetime] = None
pedimento_date: Optional[datetime] = None
payment_date: Optional[datetime] = None payment_date: Optional[datetime] = None
rectification_payment_date: Optional[datetime] = None rectification_payment_date: Optional[datetime] = None
extraction_date: Optional[datetime] = None extraction_date: Optional[datetime] = None
@@ -53,8 +46,6 @@ class PedimentoDatesUpdate(BaseModel):
original_date: Optional[datetime] = None original_date: Optional[datetime] = None
start_date: Optional[datetime] = None start_date: Optional[datetime] = None
end_date: Optional[datetime] = None end_date: Optional[datetime] = None
capture_date: Optional[datetime] = None
capture_time: Optional[time] = None
class PedimentoDatesResponse(PedimentoDatesBase): class PedimentoDatesResponse(PedimentoDatesBase):

View File

@@ -1,6 +1,6 @@
from datetime import datetime from datetime import datetime
from datetime import time as datetime_time from datetime import time as datetime_time
from typing import TYPE_CHECKING from typing import TYPE_CHECKING, Optional
from api.v1.common.base_models import TenantScopedMixin, TimestampMixin from api.v1.common.base_models import TenantScopedMixin, TimestampMixin
from core.database import Base from core.database import Base
@@ -48,16 +48,16 @@ class PedimentoDates(Base, TenantScopedMixin, TimestampMixin):
id: Mapped[int] = mapped_column(Integer) id: Mapped[int] = mapped_column(Integer)
pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False) pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False)
entry_date: Mapped[datetime] = mapped_column(DateTime) entry_date: Mapped[Optional[datetime]] = mapped_column(DateTime)
pedimento_date: Mapped[datetime] = mapped_column(DateTime) pedimento_date: Mapped[datetime] = mapped_column(DateTime)
payment_date: Mapped[datetime] = mapped_column(DateTime) payment_date: Mapped[datetime] = mapped_column(DateTime)
rectification_payment_date: Mapped[datetime] = mapped_column(DateTime) rectification_payment_date: Mapped[Optional[datetime]] = mapped_column(DateTime)
extraction_date: Mapped[datetime] = mapped_column(DateTime) extraction_date: Mapped[Optional[datetime]] = mapped_column(DateTime)
submission_date: Mapped[datetime] = mapped_column(DateTime) submission_date: Mapped[Optional[datetime]] = mapped_column(DateTime)
eucan_date: Mapped[datetime] = mapped_column(DateTime) eucan_date: Mapped[Optional[datetime]] = mapped_column(DateTime)
original_date: Mapped[datetime] = mapped_column(DateTime) original_date: Mapped[Optional[datetime]] = mapped_column(DateTime)
start_date: Mapped[datetime] = mapped_column(DateTime) start_date: Mapped[Optional[datetime]] = mapped_column(DateTime)
end_date: Mapped[datetime] = mapped_column(DateTime) end_date: Mapped[Optional[datetime]] = mapped_column(DateTime)
capture_date: Mapped[datetime] = mapped_column(DateTime) capture_date: Mapped[datetime] = mapped_column(DateTime)
capture_time: Mapped[datetime_time] = mapped_column(Time) capture_time: Mapped[datetime_time] = mapped_column(Time)

View File

@@ -15,11 +15,16 @@ router = APIRouter(prefix="/code-pedimento-regimens")
def list_code_pedimento_regimens( def list_code_pedimento_regimens(
page: int = Query(1, ge=1, description="Número de página"), page: int = Query(1, ge=1, description="Número de página"),
page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"), page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"),
code: str = Query(None, description="Filter by code"),
db: Session = Depends(get_core_db), db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user), current_user: dict = Depends(get_current_user),
): ):
skip = (page - 1) * page_size skip = (page - 1) * page_size
query = db.query(CodePedimentoRegimen) query = db.query(CodePedimentoRegimen)
if code is not None:
query = query.filter(CodePedimentoRegimen.pedimento_code == code)
items = query.offset(skip).limit(page_size).all() items = query.offset(skip).limit(page_size).all()
total = query.count() total = query.count()
return { return {

View File

@@ -176,24 +176,32 @@
/> />
</div> </div>
<!-- Clave del Pedimento --> <!-- Clave del Pedimento -->
<div class="space-y-2"> <div class="space-y-2">
<Label for="pedimento_code">Clave del Pedimento</Label> <Label for="pedimento_code">Clave del Pedimento</Label>
<Select.Root <Select.Root
type="single" type="single"
value={formData.pedimento_code || ''} value={formData.pedimento_code || ''}
onValueChange={(v: string) => formData.pedimento_code = v ?? ''} onValueChange={(v: string) => formData.pedimento_code = v ?? ''}
> >
<Select.Trigger class="w-full"> <Select.Trigger class="w-full">
{pedimentoCodes.find(o => o.code === formData.pedimento_code)?.description || 'Seleccionar...'} <span class="truncate">
</Select.Trigger> {pedimentoCodes.find(o => o.code === formData.pedimento_code)?.code || 'Seleccionar...'}
<Select.Content> </span>
{#each pedimentoCodes as code} </Select.Trigger>
<Select.Item value={code.code} label={`${code.code} - ${code.description}`} /> <Select.Content class="max-w-[600px]">
{/each} {#each pedimentoCodes as code}
</Select.Content> <Select.Item value={code.code}>
</Select.Root> <span class="truncate overflow-hidden text-ellipsis whitespace-nowrap" title={`${code.code} - ${code.description}`}>
</div> <!-- Régimen --> {code.code} - {code.description}
</span>
</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
<!-- Régimen -->
<div class="space-y-2"> <div class="space-y-2">
<Label for="regime">Régimen</Label> <Label for="regime">Régimen</Label>
<Input <Input

View File

@@ -69,7 +69,7 @@ class CompanyStore {
// Si no hay datos pre-cargados, hacer fetch (fallback) // Si no hay datos pre-cargados, hacer fetch (fallback)
this._loading = true; this._loading = true;
try { try {
const response = await fetch('/api/company/my-companies'); const response = await fetch('/api/v1/a76/company/my-companies');
if (response.ok) { if (response.ok) {
const newCompanies = await response.json(); const newCompanies = await response.json();

View File

@@ -18,8 +18,19 @@
// Importar solo la API de pedimentos // Importar solo la API de pedimentos
import { pedimentosApi, type CreatePedimentoData, type UpdatePedimentoData } from '$lib/api/dashboard/a76/pedimentos'; import { pedimentosApi, type CreatePedimentoData, type UpdatePedimentoData } from '$lib/api/dashboard/a76/pedimentos';
import type { PedimentoCode } from '$lib/api/dashboard/refrence_data/pedimento_codes';
let { data }: { data: PageData } = $props(); interface ExtendedPageData {
pedimentoId?: number | null;
pedimento?: any;
isCreate?: boolean;
pedimentoCodes?: PedimentoCode[];
user?: any;
companies?: any[];
authenticated?: boolean;
}
let { data }: { data: ExtendedPageData } = $props();
let activeTab = $state('general'); let activeTab = $state('general');
let saving = $state(false); let saving = $state(false);
@@ -27,7 +38,7 @@
let success = $state(false); let success = $state(false);
// ID del pedimento // ID del pedimento
let pedimentoId = $state<number | null>(data.pedimentoId); let pedimentoId = $state<number | null>(data.pedimentoId ?? null);
// Referencias a los componentes de formulario para obtener sus datos // Referencias a los componentes de formulario para obtener sus datos
let generalFormData = $state<any>(null); let generalFormData = $state<any>(null);

View File

@@ -22,7 +22,7 @@
# se actualiza con el ID real del tenant creado en PostgreSQL. # se actualiza con el ID real del tenant creado en PostgreSQL.
############################################################################### ###############################################################################
set -e # Salir si hay algún error # set -e # Comentado para permitir que el script continúe aunque algunos comandos fallen (ej: mapper ya existe)
# Colores para output # Colores para output
RED='\033[0;31m' RED='\033[0;31m'
@@ -309,87 +309,84 @@ echo -e "\n${YELLOW}[5/8] Configurando mappers para tenant_id...${NC}"
if [ -n "$BACKEND_CLIENT_ID" ]; then if [ -n "$BACKEND_CLIENT_ID" ]; then
echo "Configurando mapper para Backend..." echo "Configurando mapper para Backend..."
# Obtener el dedicated scope del cliente backend # Verificar si el mapper tenant_id ya existe en el cliente
BACKEND_SCOPES=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients/${BACKEND_CLIENT_ID}/optional-client-scopes" \ MAPPER_TENANT_EXISTS=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients/${BACKEND_CLIENT_ID}/protocol-mappers/models" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json") -H "Content-Type: application/json" | grep -o "\"name\":\"tenant-id-mapper\"")
# Buscar el scope dedicado if [ -z "$MAPPER_TENANT_EXISTS" ]; then
BACKEND_DEDICATED_SCOPE_ID=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/client-scopes" \ # Crear mapper para tenant_id directamente en el cliente
-H "Authorization: Bearer ${ACCESS_TOKEN}" \ CREATE_MAPPER_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients/${BACKEND_CLIENT_ID}/protocol-mappers/models" \
-H "Content-Type: application/json" | grep -o "\"id\":\"[^\"]*\",\"name\":\"anexo76-backend-dedicated\"" | grep -o "\"id\":\"[^\"]*" | sed 's/"id":"//')
if [ -n "$BACKEND_DEDICATED_SCOPE_ID" ]; then
# Verificar si el mapper tenant_id ya existe
MAPPER_TENANT_EXISTS=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/client-scopes/${BACKEND_DEDICATED_SCOPE_ID}/protocol-mappers/models" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" | grep -o "\"name\":\"tenant-id-mapper\"") -H "Content-Type: application/json" \
-d '{
"name": "tenant-id-mapper",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-attribute-mapper",
"config": {
"user.attribute": "tenant_id",
"claim.name": "tenant_id",
"jsonType.label": "String",
"id.token.claim": "true",
"access.token.claim": "true",
"userinfo.token.claim": "true"
}
}')
if [ -z "$MAPPER_TENANT_EXISTS" ]; then HTTP_CODE=$(echo "$CREATE_MAPPER_RESPONSE" | tail -n1)
# Crear mapper para tenant_id if [ "$HTTP_CODE" = "201" ]; then
curl -s -X POST "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/client-scopes/${BACKEND_DEDICATED_SCOPE_ID}/protocol-mappers/models" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "tenant-id-mapper",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-attribute-mapper",
"config": {
"user.attribute": "tenant_id",
"claim.name": "tenant_id",
"jsonType.label": "String",
"id.token.claim": "true",
"access.token.claim": "true",
"userinfo.token.claim": "true"
}
}'
echo -e "${GREEN}✓ Mapper tenant_id creado para Backend${NC}" echo -e "${GREEN}✓ Mapper tenant_id creado para Backend${NC}"
else else
echo -e "${YELLOW}Mapper tenant_id ya existe para Backend${NC}" echo -e "${YELLOW}Error al crear mapper para Backend (HTTP ${HTTP_CODE})${NC}"
echo "Respuesta: $(echo "$CREATE_MAPPER_RESPONSE" | head -n -1)"
fi fi
else
echo -e "${YELLOW}⚠ Mapper tenant_id ya existe para Backend${NC}"
fi fi
fi fi
# 4.2 Configurar mapper para Frontend # 4.2 Configurar mapper para Frontend
if [ -n "$FRONTEND_CLIENT_ID" ]; then if [ -n "$FRONTEND_CLIENT_ID" ]; then
echo "Configurando mapper para Frontend..." echo "Configurando mapper para Frontend..."
# Buscar el scope dedicado del frontend # Verificar si el mapper tenant_id ya existe en el cliente
FRONTEND_DEDICATED_SCOPE_ID=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/client-scopes" \ MAPPER_TENANT_EXISTS=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients/${FRONTEND_CLIENT_ID}/protocol-mappers/models" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" | grep -o "\"id\":\"[^\"]*\",\"name\":\"anexo76-frontend-dedicated\"" | grep -o "\"id\":\"[^\"]*" | sed 's/"id":"//') -H "Content-Type: application/json" | grep -o "\"name\":\"tenant-id-mapper\"")
if [ -n "$FRONTEND_DEDICATED_SCOPE_ID" ]; then if [ -z "$MAPPER_TENANT_EXISTS" ]; then
# Verificar si el mapper tenant_id ya existe # Crear mapper para tenant_id directamente en el cliente
MAPPER_TENANT_EXISTS=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/client-scopes/${FRONTEND_DEDICATED_SCOPE_ID}/protocol-mappers/models" \ CREATE_MAPPER_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients/${FRONTEND_CLIENT_ID}/protocol-mappers/models" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" | grep -o "\"name\":\"tenant-id-mapper\"") -H "Content-Type: application/json" \
-d '{
"name": "tenant-id-mapper",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-attribute-mapper",
"config": {
"user.attribute": "tenant_id",
"claim.name": "tenant_id",
"jsonType.label": "String",
"id.token.claim": "true",
"access.token.claim": "true",
"userinfo.token.claim": "true"
}
}')
if [ -z "$MAPPER_TENANT_EXISTS" ]; then HTTP_CODE=$(echo "$CREATE_MAPPER_RESPONSE" | tail -n1)
# Crear mapper para tenant_id if [ "$HTTP_CODE" = "201" ]; then
curl -s -X POST "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/client-scopes/${FRONTEND_DEDICATED_SCOPE_ID}/protocol-mappers/models" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "tenant-id-mapper",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-attribute-mapper",
"config": {
"user.attribute": "tenant_id",
"claim.name": "tenant_id",
"jsonType.label": "String",
"id.token.claim": "true",
"access.token.claim": "true",
"userinfo.token.claim": "true"
}
}'
echo -e "${GREEN}✓ Mapper tenant_id creado para Frontend${NC}" echo -e "${GREEN}✓ Mapper tenant_id creado para Frontend${NC}"
else else
echo -e "${YELLOW}Mapper tenant_id ya existe para Frontend${NC}" echo -e "${YELLOW}Error al crear mapper para Frontend (HTTP ${HTTP_CODE})${NC}"
echo "Respuesta: $(echo "$CREATE_MAPPER_RESPONSE" | head -n -1)"
fi fi
else
echo -e "${YELLOW}⚠ Mapper tenant_id ya existe para Frontend${NC}"
fi fi
fi fi
############################################################################### ###############################################################################
# 6. Crear usuario demo en Keycloak # 6. Crear usuario demo en Keycloak
############################################################################### ###############################################################################