feature/seed-digitalizacion
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
"""seed document_types_digitization
|
||||
|
||||
Revision ID: d2e3f4a5b6c7
|
||||
Revises: f1a2b3c4d5e6
|
||||
Create Date: 2026-05-25
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from api.v1.modules.a76.doc_types_dig.seed import seed as doc_types_dig_seed
|
||||
|
||||
revision: str = "d2e3f4a5b6c7"
|
||||
down_revision: Union[str, None] = "f1a2b3c4d5e6"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# Quitar columna active (no se usa en ningún flujo)
|
||||
op.drop_column('document_types_digitization', 'active', schema='a76')
|
||||
|
||||
def format_value(val):
|
||||
if val is None or str(val).strip() == "" or str(val).upper() == "NONE":
|
||||
return "NULL"
|
||||
return f"'{str(val).replace(chr(39), chr(39)*2)}'"
|
||||
|
||||
bind = op.get_bind()
|
||||
session = Session(bind=bind)
|
||||
companies = session.execute(
|
||||
text("SELECT tenant_id, id FROM a76.company WHERE deleted_at IS NULL")
|
||||
).fetchall()
|
||||
|
||||
for tenant_id, company_id in companies:
|
||||
values = ", ".join([
|
||||
f"({format_value(code)}, {format_value(description)}, {tenant_id}, {company_id})"
|
||||
for code, description in doc_types_dig_seed
|
||||
])
|
||||
if values:
|
||||
session.execute(text(f"""
|
||||
INSERT INTO a76.document_types_digitization (code, description, tenant_id, company_id)
|
||||
VALUES {values}
|
||||
ON CONFLICT (tenant_id, company_id, code) DO NOTHING;
|
||||
"""))
|
||||
session.commit()
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.add_column(
|
||||
'document_types_digitization',
|
||||
sa.Column('active', sa.Boolean(), nullable=False, server_default=sa.text('true')),
|
||||
schema='a76',
|
||||
)
|
||||
@@ -6,7 +6,6 @@ class DocumentTypeDigitizationBase(BaseModel):
|
||||
|
||||
code: str = Field(..., max_length=10, description="Código del tipo de documento")
|
||||
description: str = Field(..., description="Descripción del tipo de documento")
|
||||
active: bool = Field(default=True, description="Indica si el tipo está activo")
|
||||
|
||||
|
||||
class DocumentTypeDigitizationCreate(DocumentTypeDigitizationBase):
|
||||
@@ -20,7 +19,6 @@ class DocumentTypeDigitizationUpdate(BaseModel):
|
||||
|
||||
code: str | None = Field(None, max_length=10)
|
||||
description: str | None = None
|
||||
active: bool | None = None
|
||||
|
||||
|
||||
class DocumentTypeDigitizationResponse(DocumentTypeDigitizationBase):
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from api.v1.common.base_models import TenantScopedMixin, TimestampMixin
|
||||
from core.database import Base
|
||||
from sqlalchemy import (
|
||||
Boolean,
|
||||
Integer,
|
||||
PrimaryKeyConstraint,
|
||||
String,
|
||||
@@ -29,4 +28,3 @@ class DocumentTypeDigitization(Base, TenantScopedMixin, TimestampMixin):
|
||||
id: Mapped[int] = mapped_column(Integer)
|
||||
code: Mapped[str] = mapped_column(String(10), nullable=False, index=True)
|
||||
description: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
active: Mapped[bool] = mapped_column(Boolean, default=True, server_default="true")
|
||||
|
||||
@@ -19,7 +19,6 @@ async def list_document_types(
|
||||
page: int = Query(1, ge=1, description='Page number'),
|
||||
page_size: int = Query(50, ge=1, le=2000, description='Page size'),
|
||||
search: Optional[str] = Query(None, description='Search by code or description'),
|
||||
active_only: bool = Query(False, description='Only active records'),
|
||||
sort_by: Optional[str] = Query('code', description='Column to sort by'),
|
||||
sort_order: str = Query('asc', pattern='^(asc|desc)$', description='Sort order'),
|
||||
db: Session = Depends(get_core_db),
|
||||
@@ -31,8 +30,6 @@ async def list_document_types(
|
||||
|
||||
if search:
|
||||
filters['search'] = search
|
||||
if active_only:
|
||||
filters['active_only'] = True
|
||||
|
||||
items, total = DocumentTypeDigitizationService.get_all(
|
||||
db,
|
||||
|
||||
40
backend/api/v1/modules/a76/doc_types_dig/seed.py
Normal file
40
backend/api/v1/modules/a76/doc_types_dig/seed.py
Normal file
@@ -0,0 +1,40 @@
|
||||
# Catálogo SAT — tipos de documentos para digitalización (Ventanilla Única)
|
||||
seed = [
|
||||
('168', 'Calca o fotografía digital del NIV del vehículo.'),
|
||||
('169', 'Aviso.'),
|
||||
('170', 'Factura.'),
|
||||
('171', 'Documento con el que se acredite la propiedad de la mercancía.'),
|
||||
('172', 'Contratos.'),
|
||||
('176', 'Documentación relacionada con la garantía otorgada en términos de los artículos 84.'),
|
||||
('177', 'Identificación Oficial.'),
|
||||
('179', 'Comprobante de domicilio.'),
|
||||
('184', 'Documento que ampara el avaluó de las mercancías.'),
|
||||
('185', 'Documentos de adjudicación judicial de las mercancías.'),
|
||||
('187', 'Solicitud de retiro de mercancías que causaron abandono.'),
|
||||
('189', 'Actas.'),
|
||||
('192', 'Escritos.'),
|
||||
('420', 'Certificado de peso o volumen.'),
|
||||
('421', 'Comprobante de la importación temporal de la embarcación debidamente formalizado.'),
|
||||
('422', 'Comprobante expedido por donataria.'),
|
||||
('423', 'Consulta en la que conste que el vehículo no se encuentra reportado como robado,'),
|
||||
('424', 'Clave Unica del Registro de Población.'),
|
||||
('425', 'Declaración de internación o extracción de cantidades en efectivo y/o documentos p'),
|
||||
('426', 'Declaración de operaciones que no confieren origen en países no parte de acuerdo'),
|
||||
('427', 'Declaración en la que se señalen los motivos por los que efectúa la devolución de m'),
|
||||
('428', 'Documentación con información que permita la identificación, análisis y control en tér'),
|
||||
('429', 'Documentación que acredite que acepta y subsana la irregularidad.'),
|
||||
('430', 'Documentación que ampara la importación temporal del vehículo de que se trate.'),
|
||||
('431', 'Documentación que compruebe que la adquisición de las mercancías fue efectuada '),
|
||||
('433', 'Documento con base en el cual se determine la procedencia y el origen de las merca'),
|
||||
('434', 'Documento con que se acredite el reintegro del IVA, en caso de que el contribuyente '),
|
||||
('435', 'Documentos previstos en la regla 8.7., fracciones I a IV de la Resolución del TLCAN.'),
|
||||
('436', 'El Documento que compruebe el cumplimiento de las regulaciones y restricciones no '),
|
||||
('438', 'Guía aérea, conocimiento de embarque o carta de porte.'),
|
||||
('439', 'Hoja con los datos de la matrícula y nombre del barco, el lugar donde se localiza y se '),
|
||||
('440', 'Manifiesto de carga.'),
|
||||
('441', 'Oficios emitidos por autoridad.'),
|
||||
('442', 'Pedimentos.'),
|
||||
('443', 'Programa IMMEX.'),
|
||||
('444', 'Relación de candados.'),
|
||||
('445', 'Relación de certificados de origen.'),
|
||||
]
|
||||
@@ -18,14 +18,6 @@ class DocumentTypeDigitizationService:
|
||||
def _normalize_description(description: str) -> str:
|
||||
return (description or '').strip()
|
||||
|
||||
@staticmethod
|
||||
def _is_truthy_filter(value: Any, default: bool = False) -> bool:
|
||||
if value is None:
|
||||
return default
|
||||
if isinstance(value, bool):
|
||||
return value
|
||||
return str(value).strip().lower() in {'1', 'true', 'yes', 'si'}
|
||||
|
||||
@staticmethod
|
||||
def get_all(
|
||||
db: Session,
|
||||
@@ -45,15 +37,8 @@ class DocumentTypeDigitizationService:
|
||||
query = query.filter(DocumentTypeDigitization.company_id == company_id)
|
||||
|
||||
filters = filters or {}
|
||||
active_only = DocumentTypeDigitizationService._is_truthy_filter(
|
||||
filters.get('active_only'),
|
||||
default=False,
|
||||
)
|
||||
search = (filters.get('search') or '').strip()
|
||||
|
||||
if active_only:
|
||||
query = query.filter(DocumentTypeDigitization.active.is_(True))
|
||||
|
||||
if search:
|
||||
like = f'%{search}%'
|
||||
query = query.filter(
|
||||
@@ -69,7 +54,6 @@ class DocumentTypeDigitizationService:
|
||||
'id': DocumentTypeDigitization.id,
|
||||
'code': DocumentTypeDigitization.code,
|
||||
'description': DocumentTypeDigitization.description,
|
||||
'active': DocumentTypeDigitization.active,
|
||||
}.get(sort_by or 'code', DocumentTypeDigitization.code)
|
||||
|
||||
if sort_order == 'desc':
|
||||
@@ -195,17 +179,3 @@ class DocumentTypeDigitizationService:
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
@staticmethod
|
||||
def delete(
|
||||
db: Session,
|
||||
id: int,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
) -> bool:
|
||||
db_obj = DocumentTypeDigitizationService.get_by_id(db, id, tenant_id, company_id)
|
||||
if not db_obj:
|
||||
return False
|
||||
|
||||
db_obj.active = False
|
||||
db.commit()
|
||||
return True
|
||||
@@ -21,6 +21,7 @@ from ..units_of_measure.seed import seed as units_of_measure_seed
|
||||
from ..fractions.historical_tariff_fractions.seed import seed as historical_tariff_fractions_seed
|
||||
from ..fractions.warning_fractions.seed import seed as warning_fractions_seed
|
||||
from ..sectors.seed import seed as sectors_seed
|
||||
from ...doc_types_dig.seed import seed as doc_types_dig_seed
|
||||
from core.context import get_user_context
|
||||
from sqlalchemy import text
|
||||
|
||||
@@ -812,6 +813,20 @@ class CompanyService:
|
||||
ON CONFLICT (key, tenant_id, company_id) DO NOTHING;
|
||||
"""))
|
||||
|
||||
# 5. Document Types for Digitization
|
||||
values_doc_types = ", ".join(
|
||||
[
|
||||
f"({format_value(code)}, {format_value(description)}, {tenant_id}, {company_id})"
|
||||
for code, description in doc_types_dig_seed
|
||||
]
|
||||
)
|
||||
if values_doc_types:
|
||||
db.execute(text(f"""
|
||||
INSERT INTO a76.document_types_digitization (code, description, tenant_id, company_id)
|
||||
VALUES {values_doc_types}
|
||||
ON CONFLICT (tenant_id, company_id, code) DO NOTHING;
|
||||
"""))
|
||||
|
||||
def get_companies_by_tenant(self, tenant_id: int) -> List[Company]:
|
||||
"""Get all companies for a tenant"""
|
||||
return (
|
||||
|
||||
@@ -4,7 +4,6 @@ export interface DocumentTypeDigitization {
|
||||
id: number;
|
||||
code: string;
|
||||
description: string;
|
||||
active: boolean;
|
||||
}
|
||||
|
||||
export interface DocumentTypeDigitizationListResponse {
|
||||
@@ -25,7 +24,6 @@ export const documentTypesDigitizationApi = {
|
||||
pageSize = 50,
|
||||
companyId: number,
|
||||
search?: string,
|
||||
activeOnly = false
|
||||
) => {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
@@ -37,15 +35,11 @@ export const documentTypesDigitizationApi = {
|
||||
params.append('search', search);
|
||||
}
|
||||
|
||||
if (activeOnly) {
|
||||
params.append('active_only', 'true');
|
||||
}
|
||||
|
||||
return api.get<DocumentTypeDigitizationListResponse>(`${BASE_URL}/?${params.toString()}`);
|
||||
},
|
||||
|
||||
getAll: (companyId: number, activeOnly = true, search?: string) => {
|
||||
return documentTypesDigitizationApi.list(1, 2000, companyId, search, activeOnly);
|
||||
getAll: (companyId: number, search?: string) => {
|
||||
return documentTypesDigitizationApi.list(1, 2000, companyId, search);
|
||||
},
|
||||
|
||||
getById: (id: number, companyId: number) => {
|
||||
|
||||
@@ -119,7 +119,7 @@
|
||||
if (docTypes.length === 0) {
|
||||
docTypesLoading = true;
|
||||
documentTypesDigitizationApi
|
||||
.getAll(companyId, true)
|
||||
.getAll(companyId)
|
||||
.then((res) => {
|
||||
docTypes = res.data?.items || [];
|
||||
})
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
tiposDocumentos = [];
|
||||
return;
|
||||
}
|
||||
const response = await documentTypesDigitizationApi.getAll(companyId, true);
|
||||
const response = await documentTypesDigitizationApi.getAll(companyId);
|
||||
tiposDocumentos = response.data?.items || [];
|
||||
} catch (error) {
|
||||
console.error('Error al cargar tipos de documentos:', error);
|
||||
|
||||
@@ -34,25 +34,6 @@ export function createColumns(): ColumnDef<DocumentTypeDigitization>[] {
|
||||
return renderSnippet(descriptionSnippet, { description: row.original.description });
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: 'active',
|
||||
header: 'Estatus',
|
||||
cell: ({ row }) => {
|
||||
const activeSnippet = createRawSnippet<[{ active: boolean }]>((getActive) => {
|
||||
const { active } = getActive();
|
||||
const className = active
|
||||
? 'border-emerald-200 bg-emerald-50 text-emerald-700'
|
||||
: 'border-slate-200 bg-slate-50 text-slate-600';
|
||||
const label = active ? 'Activo' : 'Inactivo';
|
||||
return {
|
||||
render: () =>
|
||||
`<span class="inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-medium ${className}">${label}</span>`
|
||||
};
|
||||
});
|
||||
|
||||
return renderSnippet(activeSnippet, { active: row.original.active });
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user