feature/layouts-csv-refact

This commit is contained in:
hreyes
2026-03-03 16:45:12 -07:00
parent 973298ccd9
commit 4c781b460a
81 changed files with 151 additions and 97 deletions

1
.gitignore vendored
View File

@@ -67,6 +67,7 @@ htmlcov/
*.dockerignore *.dockerignore
postgres-data/ postgres-data/
backend/uploads/ backend/uploads/
backend/layouts/imports/
docker-compose.yml docker-compose.yml
.mypy_cache/ .mypy_cache/

View File

@@ -42,14 +42,24 @@ anexo76/
│ ├── .env.example # Variables de entorno de ejemplo │ ├── .env.example # Variables de entorno de ejemplo
│ ├── core/ # Módulos core │ ├── core/ # Módulos core
│ │ ├── config.py # Configuración centralizada │ │ ├── config.py # Configuración centralizada
│ │ ├── paths.py # Rutas base y layout_path() para importación CSV
│ │ ├── database.py # Configuración de BD multi-tenant │ │ ├── database.py # Configuración de BD multi-tenant
│ │ ├── security.py # Autenticación y autorización │ │ ├── security.py # Autenticación y autorización
│ │ └── middleware.py # Middlewares personalizados │ │ └── middleware.py # Middlewares personalizados
│ ├── layouts/ # Directorio de datos: temp y errors de importación CSV (creado al arranque)
│ └── api/ │ └── api/
│ └── v1/ │ └── v1/
│ ├── router.py # Router principal API v1 │ ├── router.py # Router principal API v1
│ └── modules/ # Módulos de negocio │ └── modules/ # Módulos de negocio
── auth/ # Autenticación ── a76/
│ ├── layouts_csv/ # Lógica centralizada de cargas por CSV (routes, tasks, validaciones por proceso)
│ │ ├── facturas/
│ │ ├── customs_brokers/
│ │ ├── clients_and_providers/
│ │ └── ... # Una carpeta por proceso de importación
│ ├── csv_templates/
│ └── ...
│ ├── auth/
│ ├── tenants/ # Gestión de tenants │ ├── tenants/ # Gestión de tenants
│ ├── licenses/ # Control de licencias │ ├── licenses/ # Control de licencias
│ └── ... │ └── ...
@@ -136,6 +146,11 @@ python -c "from core.database import init_db; init_db()"
``` ```
docker build -t dev.aduanasoft.com/anexo76/backend:latest -f ./backend/Dockerfile ./backend docker build -t dev.aduanasoft.com/anexo76/backend:latest -f ./backend/Dockerfile ./backend
```
**Importación CSV y workers Celery**: La lógica de cargas por CSV está en `api/v1/modules/a76/layouts_csv/` (una carpeta por proceso). Los workers Celery deben cargar los módulos `api.v1.modules.a76.layouts_csv.<proceso>.tasks`. El directorio de trabajo del worker debe ser la raíz del backend para que `layout_path("imports", "temp")` y `layout_path("imports", "errors")` apunten a los mismos directorios que la API. Si API y worker comparten volumen, los archivos temporales y de errores se escriben en `backend/layouts/imports/temp` y `backend/layouts/imports/errors`.
```
docker build \ docker build \
--build-arg VITE_API_URL=https://anexo76-dev.aduanasoft.com/api/ \ --build-arg VITE_API_URL=https://anexo76-dev.aduanasoft.com/api/ \
--build-arg VITE_KEYCLOAK_URL=https://anexo76-dev.aduanasoft.com/kcauth/ \ --build-arg VITE_KEYCLOAK_URL=https://anexo76-dev.aduanasoft.com/kcauth/ \
@@ -165,6 +180,8 @@ pip install -r requirements.txt
uvicorn main:app --reload uvicorn main:app --reload
``` ```
Los directorios `backend/layouts/imports/temp` y `backend/layouts/imports/errors` se crean automáticamente al arrancar el backend para la importación CSV.
### Frontend ### Frontend
```bash ```bash

View File

@@ -5,7 +5,7 @@ Mismo patrón que parts y classes: upload → scan → status → commit.
from fastapi import APIRouter from fastapi import APIRouter
from .imports.routes import router as imports_router from api.v1.modules.a76.layouts_csv.boms.routes import router as imports_router
router = APIRouter() router = APIRouter()

View File

@@ -12,7 +12,7 @@ from api.v1.common.tenant_crud_routes import TenantCRUDRoutes, validate_access_t
from .dto import ClassCreateDTO, ClassCreateDTOFA, ClassResponseDTO, ClassResponseDTOFA, ClassUpdateDTO, ClassWithFADataResponse from .dto import ClassCreateDTO, ClassCreateDTOFA, ClassResponseDTO, ClassResponseDTOFA, ClassUpdateDTO, ClassWithFADataResponse
from .service import ClassService from .service import ClassService
from .imports.routes import router as imports_router from api.v1.modules.a76.layouts_csv.classes.routes import router as imports_router
# Create a new router for custom endpoints # Create a new router for custom endpoints
router = APIRouter() router = APIRouter()

View File

@@ -20,7 +20,7 @@ from .dto import (
) )
from .service import ClientProviderService from .service import ClientProviderService
from .models import ClientProvider from .models import ClientProvider
from .imports.routes import router as imports_router from api.v1.modules.a76.layouts_csv.clients_and_providers.routes import router as imports_router
# Create main router to add custom endpoints # Create main router to add custom endpoints
router = APIRouter(prefix="/clients-providers") router = APIRouter(prefix="/clients-providers")

View File

@@ -7,35 +7,35 @@ import io
from typing import Dict, List, Optional from typing import Dict, List, Optional
# Importar configs de cada módulo # Importar configs de cada módulo
from api.v1.modules.a76.imports.template_config import ( from api.v1.modules.a76.layouts_csv.facturas.template_config import (
TEMPLATE_COLUMNS as IMPORTS_TEMPLATE_COLUMNS, TEMPLATE_COLUMNS as IMPORTS_TEMPLATE_COLUMNS,
_resolve_template_columns as resolve_imports_template, _resolve_template_columns as resolve_imports_template,
) )
from api.v1.modules.a76.parts.imports.template_config import TEMPLATE_COLUMNS as PARTS_TEMPLATE_COLUMNS from api.v1.modules.a76.layouts_csv.parts.template_config import TEMPLATE_COLUMNS as PARTS_TEMPLATE_COLUMNS
from api.v1.modules.a76.boms.imports.template_config import TEMPLATE_COLUMNS as BOMS_TEMPLATE_COLUMNS from api.v1.modules.a76.layouts_csv.boms.template_config import TEMPLATE_COLUMNS as BOMS_TEMPLATE_COLUMNS
from api.v1.modules.a76.classes.imports.template_config import TEMPLATE_COLUMNS as CLASSES_TEMPLATE_COLUMNS from api.v1.modules.a76.layouts_csv.classes.template_config import TEMPLATE_COLUMNS as CLASSES_TEMPLATE_COLUMNS
from api.v1.modules.a76.customs_brokers.imports.template_config import ( from api.v1.modules.a76.layouts_csv.customs_brokers.template_config import (
TEMPLATE_COLUMNS as CUSTOMS_BROKERS_TEMPLATE_COLUMNS, TEMPLATE_COLUMNS as CUSTOMS_BROKERS_TEMPLATE_COLUMNS,
) )
from api.v1.modules.a76.clients_and_providers.imports.template_config import ( from api.v1.modules.a76.layouts_csv.clients_and_providers.template_config import (
TEMPLATE_COLUMNS as CLIENTS_PROVIDERS_TEMPLATE_COLUMNS, TEMPLATE_COLUMNS as CLIENTS_PROVIDERS_TEMPLATE_COLUMNS,
) )
from api.v1.modules.a76.general_catalogs.exchange_rate.imports.template_config import ( from api.v1.modules.a76.layouts_csv.exchange_rate.template_config import (
TEMPLATE_COLUMNS as EXCHANGE_RATE_TEMPLATE_COLUMNS, TEMPLATE_COLUMNS as EXCHANGE_RATE_TEMPLATE_COLUMNS,
) )
from api.v1.modules.a76.general_catalogs.fractions.us_tariff_fractions.imports.template_config import ( from api.v1.modules.a76.layouts_csv.us_tariff_fractions.template_config import (
TEMPLATE_COLUMNS as US_TARIFF_FRACTIONS_TEMPLATE_COLUMNS, TEMPLATE_COLUMNS as US_TARIFF_FRACTIONS_TEMPLATE_COLUMNS,
) )
from api.v1.modules.a76.pedmientos.imports.template_config import ( from api.v1.modules.a76.layouts_csv.pedmientos.template_config import (
TEMPLATE_COLUMNS as PEDIMENTOS_TEMPLATE_COLUMNS, TEMPLATE_COLUMNS as PEDIMENTOS_TEMPLATE_COLUMNS,
) )
from api.v1.modules.a76.transportation.vehicles.imports.template_config import ( from api.v1.modules.a76.layouts_csv.vehicles.template_config import (
TEMPLATE_COLUMNS as VEHICLES_TEMPLATE_COLUMNS, TEMPLATE_COLUMNS as VEHICLES_TEMPLATE_COLUMNS,
) )
from api.v1.modules.a76.transportation.drivers.imports.template_config import ( from api.v1.modules.a76.layouts_csv.drivers.template_config import (
TEMPLATE_COLUMNS as DRIVERS_TEMPLATE_COLUMNS, TEMPLATE_COLUMNS as DRIVERS_TEMPLATE_COLUMNS,
) )
from api.v1.modules.a76.transportation.trailers.imports.template_config import ( from api.v1.modules.a76.layouts_csv.trailers.template_config import (
TEMPLATE_COLUMNS as TRAILERS_TEMPLATE_COLUMNS, TEMPLATE_COLUMNS as TRAILERS_TEMPLATE_COLUMNS,
) )

View File

@@ -1,2 +0,0 @@
# CSV import flow for Agentes Aduanales (Customs Brokers).
# Replicates the same two-phase flow as a76.imports: upload → scan → waiting_confirmation → commit.

View File

@@ -7,7 +7,7 @@ from core.security import get_current_user, validate_access_to_resource
from api.v1.common.tenant_crud_routes import TenantCRUDRoutes from api.v1.common.tenant_crud_routes import TenantCRUDRoutes
from . import dto, services from . import dto, services
from .imports.routes import router as imports_router from ..layouts_csv.customs_brokers.routes import router as imports_router
router = APIRouter() router = APIRouter()

View File

@@ -35,7 +35,7 @@ from fastapi import APIRouter
custom_router = APIRouter(prefix="/exchange-rate", tags=[]) custom_router = APIRouter(prefix="/exchange-rate", tags=[])
# CSV import: add to custom_router BEFORE including it in master, so /exchange-rate/imports/* is registered # CSV import: add to custom_router BEFORE including it in master, so /exchange-rate/imports/* is registered
from .imports.routes import router as imports_router from api.v1.modules.a76.layouts_csv.exchange_rate.routes import router as imports_router
custom_router.include_router(imports_router, prefix="/imports", tags=["exchange_rate / csv_import"]) custom_router.include_router(imports_router, prefix="/imports", tags=["exchange_rate / csv_import"])
@custom_router.get("/test-ping") @custom_router.get("/test-ping")

View File

@@ -31,7 +31,7 @@ crud_router = TenantCRUDRoutes(
) )
# Master router with prefix so all routes live under /us-tariff-fractions # Master router with prefix so all routes live under /us-tariff-fractions
from .imports.routes import router as imports_router from api.v1.modules.a76.layouts_csv.us_tariff_fractions.routes import router as imports_router
main_router = APIRouter(prefix="/us-tariff-fractions", tags=["a76 / general catalogs / us tariff fractions"]) main_router = APIRouter(prefix="/us-tariff-fractions", tags=["a76 / general catalogs / us tariff fractions"])
main_router.include_router(imports_router, prefix="/imports", tags=["us_tariff_fractions / csv_import"]) main_router.include_router(imports_router, prefix="/imports", tags=["us_tariff_fractions / csv_import"])
main_router.include_router(crud_router.router) main_router.include_router(crud_router.router)

View File

@@ -0,0 +1 @@
# Lógica centralizada de cargas por CSV (routes, tasks, validaciones, template_config por proceso)

View File

@@ -14,6 +14,7 @@ from typing import Dict, Any
from core.celery_app import celery_app from core.celery_app import celery_app
from core.database import get_core_db from core.database import get_core_db
from core.paths import layout_path
from core.security import get_current_user, validate_access_to_resource from core.security import get_current_user, validate_access_to_resource
from .schemas import ImportJobResponse from .schemas import ImportJobResponse
@@ -78,7 +79,7 @@ async def upload_import_file(
raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.") raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.")
try: try:
upload_dir = os.path.join(os.getcwd(), "uploads", "temp") upload_dir = layout_path("imports", "temp")
os.makedirs(upload_dir, exist_ok=True) os.makedirs(upload_dir, exist_ok=True)
with open(os.path.join(upload_dir, f"bom_{job_id}.csv"), "wb") as f: with open(os.path.join(upload_dir, f"bom_{job_id}.csv"), "wb") as f:
f.write(contents) f.write(contents)

View File

@@ -15,6 +15,7 @@ from typing import Dict, Any, Optional, List, Set
from core.celery_app import celery_app from core.celery_app import celery_app
from core.database import CoreSessionLocal from core.database import CoreSessionLocal
from core.paths import layout_path
from .template_config import row_from_template from .template_config import row_from_template
@@ -33,7 +34,7 @@ def _get_redis():
def _worker_upload_dir() -> str: def _worker_upload_dir() -> str:
return os.path.join(os.getcwd(), "uploads", "temp") return layout_path("imports", "temp")
def _ensure_worker_has_file_from_redis(job_id: str) -> Optional[str]: def _ensure_worker_has_file_from_redis(job_id: str) -> Optional[str]:
@@ -162,7 +163,7 @@ def scan_file(self, job_id: str, config: str = None):
return {"status": "failed", "error": "Archivo no encontrado (expirado o no subido). Sube de nuevo."} return {"status": "failed", "error": "Archivo no encontrado (expirado o no subido). Sube de nuevo."}
_ensure_worker_has_meta_from_redis(job_id, file_path) _ensure_worker_has_meta_from_redis(job_id, file_path)
error_dir = os.path.join(os.path.dirname(file_path).replace("temp", "errors"), "") error_dir = layout_path("imports", "errors")
os.makedirs(error_dir, exist_ok=True) os.makedirs(error_dir, exist_ok=True)
error_path = os.path.join(error_dir, f"bom_{job_id}.jsonl") error_path = os.path.join(error_dir, f"bom_{job_id}.jsonl")
@@ -306,8 +307,7 @@ def insert_valid_rows(self, job_id: str):
else: else:
_ensure_worker_has_meta_from_redis(job_id, file_path) _ensure_worker_has_meta_from_redis(job_id, file_path)
base_dir = os.path.dirname(file_path) error_dir = layout_path("imports", "errors")
error_dir = base_dir.replace("temp", "errors")
error_path = os.path.join(error_dir, f"bom_{job_id}.jsonl") error_path = os.path.join(error_dir, f"bom_{job_id}.jsonl")
error_lines = set() error_lines = set()

View File

@@ -14,6 +14,7 @@ from typing import Dict, Any
from core.celery_app import celery_app from core.celery_app import celery_app
from core.database import get_core_db from core.database import get_core_db
from core.paths import layout_path
from core.security import get_current_user, validate_access_to_resource from core.security import get_current_user, validate_access_to_resource
from .schemas import ImportJobResponse from .schemas import ImportJobResponse
@@ -78,7 +79,7 @@ async def upload_import_file(
raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.") raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.")
try: try:
upload_dir = os.path.join(os.getcwd(), "uploads", "temp") upload_dir = layout_path("imports", "temp")
os.makedirs(upload_dir, exist_ok=True) os.makedirs(upload_dir, exist_ok=True)
with open(os.path.join(upload_dir, f"cls_{job_id}.csv"), "wb") as f: with open(os.path.join(upload_dir, f"cls_{job_id}.csv"), "wb") as f:
f.write(contents) f.write(contents)

View File

@@ -13,6 +13,7 @@ from typing import Dict, Any, Optional, List, Set
from core.celery_app import celery_app from core.celery_app import celery_app
from core.database import CoreSessionLocal from core.database import CoreSessionLocal
from core.paths import layout_path
from .template_config import row_from_template from .template_config import row_from_template
@@ -31,7 +32,7 @@ def _get_redis():
def _worker_upload_dir() -> str: def _worker_upload_dir() -> str:
return os.path.join(os.getcwd(), "uploads", "temp") return layout_path("imports", "temp")
def _ensure_worker_has_file_from_redis(job_id: str) -> Optional[str]: def _ensure_worker_has_file_from_redis(job_id: str) -> Optional[str]:
@@ -156,7 +157,7 @@ def scan_file(self, job_id: str, config: str = None):
return {"status": "failed", "error": "Archivo no encontrado (expirado o no subido). Sube de nuevo."} return {"status": "failed", "error": "Archivo no encontrado (expirado o no subido). Sube de nuevo."}
_ensure_worker_has_meta_from_redis(job_id, file_path) _ensure_worker_has_meta_from_redis(job_id, file_path)
error_dir = os.path.join(os.path.dirname(file_path).replace("temp", "errors"), "") error_dir = layout_path("imports", "errors")
os.makedirs(error_dir, exist_ok=True) os.makedirs(error_dir, exist_ok=True)
error_path = os.path.join(error_dir, f"cls_{job_id}.jsonl") error_path = os.path.join(error_dir, f"cls_{job_id}.jsonl")
@@ -310,8 +311,7 @@ def insert_valid_rows(self, job_id: str):
else: else:
_ensure_worker_has_meta_from_redis(job_id, file_path) _ensure_worker_has_meta_from_redis(job_id, file_path)
base_dir = os.path.dirname(file_path) error_dir = layout_path("imports", "errors")
error_dir = base_dir.replace("temp", "errors")
error_path = os.path.join(error_dir, f"cls_{job_id}.jsonl") error_path = os.path.join(error_dir, f"cls_{job_id}.jsonl")
error_lines = set() error_lines = set()

View File

@@ -14,6 +14,7 @@ from typing import Dict, Any
from core.celery_app import celery_app from core.celery_app import celery_app
from core.database import get_core_db from core.database import get_core_db
from core.paths import layout_path
from core.security import get_current_user, validate_access_to_resource from core.security import get_current_user, validate_access_to_resource
from .schemas import ImportJobResponse from .schemas import ImportJobResponse
@@ -81,7 +82,7 @@ async def upload_import_file(
raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.") raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.")
try: try:
upload_dir = os.path.join(os.getcwd(), "uploads", "temp") upload_dir = layout_path("imports", "temp")
os.makedirs(upload_dir, exist_ok=True) os.makedirs(upload_dir, exist_ok=True)
with open(os.path.join(upload_dir, f"cp_{job_id}.csv"), "wb") as f: with open(os.path.join(upload_dir, f"cp_{job_id}.csv"), "wb") as f:
f.write(contents) f.write(contents)

View File

@@ -13,6 +13,7 @@ from typing import Dict, Any, Optional, List
from core.celery_app import celery_app from core.celery_app import celery_app
from core.database import CoreSessionLocal from core.database import CoreSessionLocal
from core.paths import layout_path
from .template_config import row_from_template from .template_config import row_from_template
from api.v1.modules.a76.clients_and_providers.models import ( from api.v1.modules.a76.clients_and_providers.models import (
@@ -37,7 +38,7 @@ def _get_redis():
def _worker_upload_dir() -> str: def _worker_upload_dir() -> str:
return os.path.join(os.getcwd(), "uploads", "temp") return layout_path("imports", "temp")
def _ensure_worker_has_file_from_redis(job_id: str) -> Optional[str]: def _ensure_worker_has_file_from_redis(job_id: str) -> Optional[str]:
@@ -165,7 +166,7 @@ def scan_file(self, job_id: str, config: str = None):
return {"status": "failed", "error": "Archivo no encontrado (expirado o no subido). Sube de nuevo."} return {"status": "failed", "error": "Archivo no encontrado (expirado o no subido). Sube de nuevo."}
_ensure_worker_has_meta_from_redis(job_id, file_path) _ensure_worker_has_meta_from_redis(job_id, file_path)
error_dir = os.path.join(os.path.dirname(file_path).replace("temp", "errors"), "") error_dir = layout_path("imports", "errors")
os.makedirs(error_dir, exist_ok=True) os.makedirs(error_dir, exist_ok=True)
error_path = os.path.join(error_dir, f"cp_{job_id}.jsonl") error_path = os.path.join(error_dir, f"cp_{job_id}.jsonl")
@@ -290,8 +291,7 @@ def insert_valid_rows(self, job_id: str):
else: else:
_ensure_worker_has_meta_from_redis(job_id, file_path) _ensure_worker_has_meta_from_redis(job_id, file_path)
base_dir = os.path.dirname(file_path) error_dir = layout_path("imports", "errors")
error_dir = base_dir.replace("temp", "errors")
error_path = os.path.join(error_dir, f"cp_{job_id}.jsonl") error_path = os.path.join(error_dir, f"cp_{job_id}.jsonl")
error_lines = set() error_lines = set()

View File

@@ -0,0 +1 @@
# layouts_csv.customs_brokers

View File

@@ -14,6 +14,7 @@ from typing import Dict, Any
from core.celery_app import celery_app from core.celery_app import celery_app
from core.database import get_core_db from core.database import get_core_db
from core.paths import layout_path
from core.security import get_current_user, validate_access_to_resource from core.security import get_current_user, validate_access_to_resource
from .schemas import ImportJobResponse from .schemas import ImportJobResponse
@@ -81,7 +82,7 @@ async def upload_import_file(
raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.") raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.")
try: try:
upload_dir = os.path.join(os.getcwd(), "uploads", "temp") upload_dir = layout_path("imports", "temp")
os.makedirs(upload_dir, exist_ok=True) os.makedirs(upload_dir, exist_ok=True)
with open(os.path.join(upload_dir, f"cb_{job_id}.csv"), "wb") as f: with open(os.path.join(upload_dir, f"cb_{job_id}.csv"), "wb") as f:
f.write(contents) f.write(contents)

View File

@@ -13,6 +13,7 @@ from typing import Dict, Any, Optional, List
from core.celery_app import celery_app from core.celery_app import celery_app
from core.database import CoreSessionLocal from core.database import CoreSessionLocal
from core.paths import layout_path
from .template_config import row_from_template from .template_config import row_from_template
@@ -32,7 +33,7 @@ def _get_redis():
def _worker_upload_dir() -> str: def _worker_upload_dir() -> str:
return os.path.join(os.getcwd(), "uploads", "temp") return layout_path("imports", "temp")
def _ensure_worker_has_file_from_redis(job_id: str) -> Optional[str]: def _ensure_worker_has_file_from_redis(job_id: str) -> Optional[str]:
@@ -120,7 +121,7 @@ def scan_file(self, job_id: str, config: str = None):
return {"status": "failed", "error": "Archivo no encontrado (expirado o no subido). Sube de nuevo."} return {"status": "failed", "error": "Archivo no encontrado (expirado o no subido). Sube de nuevo."}
_ensure_worker_has_meta_from_redis(job_id, file_path) _ensure_worker_has_meta_from_redis(job_id, file_path)
error_dir = os.path.join(os.path.dirname(file_path).replace("temp", "errors"), "") error_dir = layout_path("imports", "errors")
os.makedirs(error_dir, exist_ok=True) os.makedirs(error_dir, exist_ok=True)
error_path = os.path.join(error_dir, f"cb_{job_id}.jsonl") error_path = os.path.join(error_dir, f"cb_{job_id}.jsonl")
@@ -245,8 +246,7 @@ def insert_valid_rows(self, job_id: str):
else: else:
_ensure_worker_has_meta_from_redis(job_id, file_path) _ensure_worker_has_meta_from_redis(job_id, file_path)
base_dir = os.path.dirname(file_path) error_dir = layout_path("imports", "errors")
error_dir = base_dir.replace("temp", "errors")
error_path = os.path.join(error_dir, f"cb_{job_id}.jsonl") error_path = os.path.join(error_dir, f"cb_{job_id}.jsonl")
error_lines = set() error_lines = set()

View File

@@ -15,6 +15,7 @@ from typing import Dict, Any
from core.celery_app import celery_app from core.celery_app import celery_app
from core.database import get_core_db from core.database import get_core_db
from core.paths import layout_path
from core.security import get_current_user, validate_access_to_resource from core.security import get_current_user, validate_access_to_resource
from .schemas import ImportJobResponse from .schemas import ImportJobResponse
@@ -81,7 +82,7 @@ async def upload_import_file(
raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.") raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.")
try: try:
upload_dir = os.path.join(os.getcwd(), "uploads", "temp") upload_dir = layout_path("imports", "temp")
os.makedirs(upload_dir, exist_ok=True) os.makedirs(upload_dir, exist_ok=True)
with open(os.path.join(upload_dir, f"drv_{job_id}.csv"), "wb") as f: with open(os.path.join(upload_dir, f"drv_{job_id}.csv"), "wb") as f:
f.write(contents) f.write(contents)

View File

@@ -13,6 +13,7 @@ from typing import Dict, Any, Optional, List
from core.celery_app import celery_app from core.celery_app import celery_app
from core.database import CoreSessionLocal from core.database import CoreSessionLocal
from core.paths import layout_path
from .template_config import row_from_template from .template_config import row_from_template
@@ -32,7 +33,7 @@ def _get_redis():
def _worker_upload_dir() -> str: def _worker_upload_dir() -> str:
return os.path.join(os.getcwd(), "uploads", "temp") return layout_path("imports", "temp")
def _ensure_worker_has_file_from_redis(job_id: str) -> Optional[str]: def _ensure_worker_has_file_from_redis(job_id: str) -> Optional[str]:
@@ -281,7 +282,7 @@ def _do_scan(job_id: str, progress_callback: Optional[Any] = None) -> Dict[str,
return {"status": "failed", "error": "Archivo no encontrado (expirado o no subido). Sube de nuevo."} return {"status": "failed", "error": "Archivo no encontrado (expirado o no subido). Sube de nuevo."}
_ensure_worker_has_meta_from_redis(job_id, file_path) _ensure_worker_has_meta_from_redis(job_id, file_path)
error_dir = os.path.join(os.path.dirname(file_path).replace("temp", "errors"), "") error_dir = layout_path("imports", "errors")
os.makedirs(error_dir, exist_ok=True) os.makedirs(error_dir, exist_ok=True)
error_path = os.path.join(error_dir, f"drv_{job_id}.jsonl") error_path = os.path.join(error_dir, f"drv_{job_id}.jsonl")
@@ -429,8 +430,7 @@ def _do_commit(job_id: str) -> Dict[str, Any]:
else: else:
_ensure_worker_has_meta_from_redis(job_id, file_path) _ensure_worker_has_meta_from_redis(job_id, file_path)
base_dir = os.path.dirname(file_path) error_dir = layout_path("imports", "errors")
error_dir = base_dir.replace("temp", "errors")
error_path = os.path.join(error_dir, f"drv_{job_id}.jsonl") error_path = os.path.join(error_dir, f"drv_{job_id}.jsonl")
error_lines = set() error_lines = set()

View File

@@ -14,6 +14,7 @@ from typing import Dict, Any
from core.celery_app import celery_app from core.celery_app import celery_app
from core.database import get_core_db from core.database import get_core_db
from core.paths import layout_path
from core.security import get_current_user, validate_access_to_resource from core.security import get_current_user, validate_access_to_resource
from .schemas import ImportJobResponse from .schemas import ImportJobResponse
@@ -81,7 +82,7 @@ async def upload_import_file(
raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.") raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.")
try: try:
upload_dir = os.path.join(os.getcwd(), "uploads", "temp") upload_dir = layout_path("imports", "temp")
os.makedirs(upload_dir, exist_ok=True) os.makedirs(upload_dir, exist_ok=True)
with open(os.path.join(upload_dir, f"er_{job_id}.csv"), "wb") as f: with open(os.path.join(upload_dir, f"er_{job_id}.csv"), "wb") as f:
f.write(contents) f.write(contents)

View File

@@ -15,6 +15,7 @@ from typing import Dict, Any, Optional, List
from core.celery_app import celery_app from core.celery_app import celery_app
from core.database import CoreSessionLocal from core.database import CoreSessionLocal
from core.paths import layout_path
from .template_config import row_from_template from .template_config import row_from_template
@@ -36,7 +37,7 @@ def _get_redis():
def _worker_upload_dir() -> str: def _worker_upload_dir() -> str:
return os.path.join(os.getcwd(), "uploads", "temp") return layout_path("imports", "temp")
def _ensure_worker_has_file_from_redis(job_id: str) -> Optional[str]: def _ensure_worker_has_file_from_redis(job_id: str) -> Optional[str]:
@@ -150,7 +151,7 @@ def scan_file(self, job_id: str, config: str = None):
return {"status": "failed", "error": "Archivo no encontrado (expirado o no subido). Sube de nuevo."} return {"status": "failed", "error": "Archivo no encontrado (expirado o no subido). Sube de nuevo."}
_ensure_worker_has_meta_from_redis(job_id, file_path) _ensure_worker_has_meta_from_redis(job_id, file_path)
error_dir = os.path.join(os.path.dirname(file_path).replace("temp", "errors"), "") error_dir = layout_path("imports", "errors")
os.makedirs(error_dir, exist_ok=True) os.makedirs(error_dir, exist_ok=True)
error_path = os.path.join(error_dir, f"er_{job_id}.jsonl") error_path = os.path.join(error_dir, f"er_{job_id}.jsonl")
@@ -274,8 +275,7 @@ def insert_valid_rows(self, job_id: str):
else: else:
_ensure_worker_has_meta_from_redis(job_id, file_path) _ensure_worker_has_meta_from_redis(job_id, file_path)
base_dir = os.path.dirname(file_path) error_dir = layout_path("imports", "errors")
error_dir = base_dir.replace("temp", "errors")
error_path = os.path.join(error_dir, f"er_{job_id}.jsonl") error_path = os.path.join(error_dir, f"er_{job_id}.jsonl")
error_lines = set() error_lines = set()

View File

@@ -11,6 +11,7 @@ from typing import Optional, Literal, Dict, Any
from core.celery_app import celery_app from core.celery_app import celery_app
from core.config import settings from core.config import settings
from core.database import get_core_db from core.database import get_core_db
from core.paths import layout_path
from core.security import get_current_user, validate_access_to_resource from core.security import get_current_user, validate_access_to_resource
from .tasks import ( from .tasks import (
@@ -88,7 +89,7 @@ async def upload_import_file(
# Optional: also write to local disk (e.g. for same-machine worker or debugging) # Optional: also write to local disk (e.g. for same-machine worker or debugging)
try: try:
upload_dir = os.path.join(os.getcwd(), "uploads", "temp") upload_dir = layout_path("imports", "temp")
os.makedirs(upload_dir, exist_ok=True) os.makedirs(upload_dir, exist_ok=True)
file_path = os.path.join(upload_dir, f"{job_id}.csv") file_path = os.path.join(upload_dir, f"{job_id}.csv")
meta_path = os.path.join(upload_dir, f"{job_id}.meta.json") meta_path = os.path.join(upload_dir, f"{job_id}.meta.json")

View File

@@ -11,6 +11,7 @@ from typing import Dict, Any, Optional, List
from core.celery_app import celery_app from core.celery_app import celery_app
from core.database import CoreSessionLocal from core.database import CoreSessionLocal
from core.paths import layout_path
from .template_config import row_from_template from .template_config import row_from_template
# Models are imported inside tasks to avoid circular dependencies and mapper initialization issues in the API process # Models are imported inside tasks to avoid circular dependencies and mapper initialization issues in the API process
@@ -37,7 +38,7 @@ def _get_redis():
def _worker_upload_dir() -> str: def _worker_upload_dir() -> str:
"""Directory on the worker for temp CSV and meta (same structure as API, but local to worker).""" """Directory on the worker for temp CSV and meta (same structure as API, but local to worker)."""
return os.path.join(os.getcwd(), "uploads", "temp") return layout_path("imports", "temp")
def _ensure_worker_has_file_from_redis(job_id: str) -> Optional[str]: def _ensure_worker_has_file_from_redis(job_id: str) -> Optional[str]:
@@ -189,8 +190,9 @@ def scan_file(self, job_id: str, model_target: str, config: str = None):
_ensure_worker_has_meta_from_redis(job_id, file_path) _ensure_worker_has_meta_from_redis(job_id, file_path)
# 2. Setup Error Log # 2. Setup Error Log
error_path = file_path.replace("temp", "errors").replace(".csv", ".jsonl") error_dir = layout_path("imports", "errors")
os.makedirs(os.path.dirname(error_path), exist_ok=True) os.makedirs(error_dir, exist_ok=True)
error_path = os.path.join(error_dir, f"{job_id}.jsonl")
total_rows = 0 total_rows = 0
error_count = 0 error_count = 0
@@ -805,7 +807,8 @@ def insert_valid_rows(self, job_id: str, model_target: str):
from api.v1.modules.a76.items.line_descriptions.models import LineDescription from api.v1.modules.a76.items.line_descriptions.models import LineDescription
from api.v1.modules.a76.parts.models import Part from api.v1.modules.a76.parts.models import Part
error_path = file_path.replace("temp", "errors").replace(".csv", ".jsonl") error_dir = layout_path("imports", "errors")
error_path = os.path.join(error_dir, f"{job_id}.jsonl")
# 1. Load Error Line Numbers (from Redis if scan ran on another worker, else from file) # 1. Load Error Line Numbers (from Redis if scan ran on another worker, else from file)
error_lines = set() error_lines = set()

View File

@@ -14,6 +14,7 @@ from typing import Dict, Any
from core.celery_app import celery_app from core.celery_app import celery_app
from core.database import get_core_db from core.database import get_core_db
from core.paths import layout_path
from core.security import get_current_user, validate_access_to_resource from core.security import get_current_user, validate_access_to_resource
from .schemas import ImportJobResponse from .schemas import ImportJobResponse
@@ -78,7 +79,7 @@ async def upload_import_file(
raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.") raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.")
try: try:
upload_dir = os.path.join(os.getcwd(), "uploads", "temp") upload_dir = layout_path("imports", "temp")
os.makedirs(upload_dir, exist_ok=True) os.makedirs(upload_dir, exist_ok=True)
with open(os.path.join(upload_dir, f"part_{job_id}.csv"), "wb") as f: with open(os.path.join(upload_dir, f"part_{job_id}.csv"), "wb") as f:
f.write(contents) f.write(contents)

View File

@@ -14,6 +14,7 @@ from typing import Dict, Any, Optional, List, Set
from core.celery_app import celery_app from core.celery_app import celery_app
from core.database import CoreSessionLocal from core.database import CoreSessionLocal
from core.paths import layout_path
from .template_config import row_from_template from .template_config import row_from_template
@@ -32,7 +33,7 @@ def _get_redis():
def _worker_upload_dir() -> str: def _worker_upload_dir() -> str:
return os.path.join(os.getcwd(), "uploads", "temp") return layout_path("imports", "temp")
def _ensure_worker_has_file_from_redis(job_id: str) -> Optional[str]: def _ensure_worker_has_file_from_redis(job_id: str) -> Optional[str]:
@@ -183,7 +184,7 @@ def scan_file(self, job_id: str, config: str = None):
return {"status": "failed", "error": "Archivo no encontrado (expirado o no subido). Sube de nuevo."} return {"status": "failed", "error": "Archivo no encontrado (expirado o no subido). Sube de nuevo."}
_ensure_worker_has_meta_from_redis(job_id, file_path) _ensure_worker_has_meta_from_redis(job_id, file_path)
error_dir = os.path.join(os.path.dirname(file_path).replace("temp", "errors"), "") error_dir = layout_path("imports", "errors")
os.makedirs(error_dir, exist_ok=True) os.makedirs(error_dir, exist_ok=True)
error_path = os.path.join(error_dir, f"part_{job_id}.jsonl") error_path = os.path.join(error_dir, f"part_{job_id}.jsonl")
@@ -368,8 +369,7 @@ def insert_valid_rows(self, job_id: str):
else: else:
_ensure_worker_has_meta_from_redis(job_id, file_path) _ensure_worker_has_meta_from_redis(job_id, file_path)
base_dir = os.path.dirname(file_path) error_dir = layout_path("imports", "errors")
error_dir = base_dir.replace("temp", "errors")
error_path = os.path.join(error_dir, f"part_{job_id}.jsonl") error_path = os.path.join(error_dir, f"part_{job_id}.jsonl")
error_lines = set() error_lines = set()

View File

@@ -14,6 +14,7 @@ from typing import Dict, Any
from core.celery_app import celery_app from core.celery_app import celery_app
from core.database import get_core_db from core.database import get_core_db
from core.paths import layout_path
from core.security import get_current_user, validate_access_to_resource from core.security import get_current_user, validate_access_to_resource
from .schemas import ImportJobResponse from .schemas import ImportJobResponse
@@ -81,7 +82,7 @@ async def upload_import_file(
raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.") raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.")
try: try:
upload_dir = os.path.join(os.getcwd(), "uploads", "temp") upload_dir = layout_path("imports", "temp")
os.makedirs(upload_dir, exist_ok=True) os.makedirs(upload_dir, exist_ok=True)
with open(os.path.join(upload_dir, f"ped_{job_id}.csv"), "wb") as f: with open(os.path.join(upload_dir, f"ped_{job_id}.csv"), "wb") as f:
f.write(contents) f.write(contents)

View File

@@ -15,6 +15,7 @@ from typing import Dict, Any, Optional, List, Set
from core.celery_app import celery_app from core.celery_app import celery_app
from core.database import CoreSessionLocal from core.database import CoreSessionLocal
from core.paths import layout_path
from .template_config import row_from_template from .template_config import row_from_template
@@ -34,7 +35,7 @@ def _get_redis():
def _worker_upload_dir() -> str: def _worker_upload_dir() -> str:
return os.path.join(os.getcwd(), "uploads", "temp") return layout_path("imports", "temp")
def _ensure_worker_has_file_from_redis(job_id: str) -> Optional[str]: def _ensure_worker_has_file_from_redis(job_id: str) -> Optional[str]:
@@ -243,7 +244,7 @@ def scan_file(self, job_id: str, config: str = None):
return {"status": "failed", "error": "Archivo no encontrado (expirado o no subido). Sube de nuevo."} return {"status": "failed", "error": "Archivo no encontrado (expirado o no subido). Sube de nuevo."}
_ensure_worker_has_meta_from_redis(job_id, file_path) _ensure_worker_has_meta_from_redis(job_id, file_path)
error_dir = os.path.join(os.path.dirname(file_path).replace("temp", "errors"), "") error_dir = layout_path("imports", "errors")
os.makedirs(error_dir, exist_ok=True) os.makedirs(error_dir, exist_ok=True)
error_path = os.path.join(error_dir, f"ped_{job_id}.jsonl") error_path = os.path.join(error_dir, f"ped_{job_id}.jsonl")
@@ -381,8 +382,7 @@ def insert_valid_rows(self, job_id: str):
else: else:
_ensure_worker_has_meta_from_redis(job_id, file_path) _ensure_worker_has_meta_from_redis(job_id, file_path)
base_dir = os.path.dirname(file_path) error_dir = layout_path("imports", "errors")
error_dir = base_dir.replace("temp", "errors")
error_path = os.path.join(error_dir, f"ped_{job_id}.jsonl") error_path = os.path.join(error_dir, f"ped_{job_id}.jsonl")
error_lines = set() error_lines = set()

View File

@@ -15,6 +15,7 @@ from typing import Dict, Any
from core.celery_app import celery_app from core.celery_app import celery_app
from core.database import get_core_db from core.database import get_core_db
from core.paths import layout_path
from core.security import get_current_user, validate_access_to_resource from core.security import get_current_user, validate_access_to_resource
from .schemas import ImportJobResponse from .schemas import ImportJobResponse
@@ -81,7 +82,7 @@ async def upload_import_file(
raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.") raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.")
try: try:
upload_dir = os.path.join(os.getcwd(), "uploads", "temp") upload_dir = layout_path("imports", "temp")
os.makedirs(upload_dir, exist_ok=True) os.makedirs(upload_dir, exist_ok=True)
with open(os.path.join(upload_dir, f"trl_{job_id}.csv"), "wb") as f: with open(os.path.join(upload_dir, f"trl_{job_id}.csv"), "wb") as f:
f.write(contents) f.write(contents)

View File

@@ -14,6 +14,7 @@ from typing import Dict, Any, Optional, List
from core.celery_app import celery_app from core.celery_app import celery_app
from core.database import CoreSessionLocal from core.database import CoreSessionLocal
from core.paths import layout_path
from .template_config import row_from_template from .template_config import row_from_template
@@ -33,7 +34,7 @@ def _get_redis():
def _worker_upload_dir() -> str: def _worker_upload_dir() -> str:
return os.path.join(os.getcwd(), "uploads", "temp") return layout_path("imports", "temp")
def _ensure_worker_has_file_from_redis(job_id: str) -> Optional[str]: def _ensure_worker_has_file_from_redis(job_id: str) -> Optional[str]:
@@ -180,7 +181,7 @@ def _do_scan(job_id: str, progress_callback: Optional[Any] = None) -> Dict[str,
return {"status": "failed", "error": "Archivo no encontrado (expirado o no subido). Sube de nuevo."} return {"status": "failed", "error": "Archivo no encontrado (expirado o no subido). Sube de nuevo."}
_ensure_worker_has_meta_from_redis(job_id, file_path) _ensure_worker_has_meta_from_redis(job_id, file_path)
error_dir = os.path.join(os.path.dirname(file_path).replace("temp", "errors"), "") error_dir = layout_path("imports", "errors")
os.makedirs(error_dir, exist_ok=True) os.makedirs(error_dir, exist_ok=True)
error_path = os.path.join(error_dir, f"trl_{job_id}.jsonl") error_path = os.path.join(error_dir, f"trl_{job_id}.jsonl")
@@ -328,8 +329,7 @@ def _do_commit(job_id: str) -> Dict[str, Any]:
else: else:
_ensure_worker_has_meta_from_redis(job_id, file_path) _ensure_worker_has_meta_from_redis(job_id, file_path)
base_dir = os.path.dirname(file_path) error_dir = layout_path("imports", "errors")
error_dir = base_dir.replace("temp", "errors")
error_path = os.path.join(error_dir, f"trl_{job_id}.jsonl") error_path = os.path.join(error_dir, f"trl_{job_id}.jsonl")
error_lines = set() error_lines = set()

View File

@@ -14,6 +14,7 @@ from typing import Dict, Any
from core.celery_app import celery_app from core.celery_app import celery_app
from core.database import get_core_db from core.database import get_core_db
from core.paths import layout_path
from core.security import get_current_user, validate_access_to_resource from core.security import get_current_user, validate_access_to_resource
from .schemas import ImportJobResponse from .schemas import ImportJobResponse
@@ -81,7 +82,7 @@ async def upload_import_file(
raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.") raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.")
try: try:
upload_dir = os.path.join(os.getcwd(), "uploads", "temp") upload_dir = layout_path("imports", "temp")
os.makedirs(upload_dir, exist_ok=True) os.makedirs(upload_dir, exist_ok=True)
with open(os.path.join(upload_dir, f"fa_{job_id}.csv"), "wb") as f: with open(os.path.join(upload_dir, f"fa_{job_id}.csv"), "wb") as f:
f.write(contents) f.write(contents)

View File

@@ -14,6 +14,7 @@ from typing import Dict, Any, Optional, List
from core.celery_app import celery_app from core.celery_app import celery_app
from core.database import CoreSessionLocal from core.database import CoreSessionLocal
from core.paths import layout_path
from .template_config import row_from_template from .template_config import row_from_template
@@ -34,7 +35,7 @@ def _get_redis():
def _worker_upload_dir() -> str: def _worker_upload_dir() -> str:
return os.path.join(os.getcwd(), "uploads", "temp") return layout_path("imports", "temp")
def _ensure_worker_has_file_from_redis(job_id: str) -> Optional[str]: def _ensure_worker_has_file_from_redis(job_id: str) -> Optional[str]:
@@ -157,7 +158,7 @@ def scan_file(self, job_id: str, config: str = None):
return {"status": "failed", "error": "Archivo no encontrado (expirado o no subido). Sube de nuevo."} return {"status": "failed", "error": "Archivo no encontrado (expirado o no subido). Sube de nuevo."}
_ensure_worker_has_meta_from_redis(job_id, file_path) _ensure_worker_has_meta_from_redis(job_id, file_path)
error_dir = os.path.join(os.path.dirname(file_path).replace("temp", "errors"), "") error_dir = layout_path("imports", "errors")
os.makedirs(error_dir, exist_ok=True) os.makedirs(error_dir, exist_ok=True)
error_path = os.path.join(error_dir, f"fa_{job_id}.jsonl") error_path = os.path.join(error_dir, f"fa_{job_id}.jsonl")
@@ -290,8 +291,7 @@ def insert_valid_rows(self, job_id: str):
else: else:
_ensure_worker_has_meta_from_redis(job_id, file_path) _ensure_worker_has_meta_from_redis(job_id, file_path)
base_dir = os.path.dirname(file_path) error_dir = layout_path("imports", "errors")
error_dir = base_dir.replace("temp", "errors")
error_path = os.path.join(error_dir, f"fa_{job_id}.jsonl") error_path = os.path.join(error_dir, f"fa_{job_id}.jsonl")
error_lines = set() error_lines = set()

View File

@@ -15,6 +15,7 @@ from typing import Dict, Any
from core.celery_app import celery_app from core.celery_app import celery_app
from core.database import get_core_db from core.database import get_core_db
from core.paths import layout_path
from core.security import get_current_user, validate_access_to_resource from core.security import get_current_user, validate_access_to_resource
from .schemas import ImportJobResponse from .schemas import ImportJobResponse
@@ -81,7 +82,7 @@ async def upload_import_file(
raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.") raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.")
try: try:
upload_dir = os.path.join(os.getcwd(), "uploads", "temp") upload_dir = layout_path("imports", "temp")
os.makedirs(upload_dir, exist_ok=True) os.makedirs(upload_dir, exist_ok=True)
with open(os.path.join(upload_dir, f"veh_{job_id}.csv"), "wb") as f: with open(os.path.join(upload_dir, f"veh_{job_id}.csv"), "wb") as f:
f.write(contents) f.write(contents)

View File

@@ -15,6 +15,7 @@ from typing import Dict, Any, Optional, List
from core.celery_app import celery_app from core.celery_app import celery_app
from core.database import CoreSessionLocal from core.database import CoreSessionLocal
from core.paths import layout_path
from .template_config import row_from_template from .template_config import row_from_template
@@ -34,7 +35,7 @@ def _get_redis():
def _worker_upload_dir() -> str: def _worker_upload_dir() -> str:
return os.path.join(os.getcwd(), "uploads", "temp") return layout_path("imports", "temp")
def _ensure_worker_has_file_from_redis(job_id: str) -> Optional[str]: def _ensure_worker_has_file_from_redis(job_id: str) -> Optional[str]:
@@ -213,7 +214,7 @@ def _do_scan(
return {"status": "failed", "error": "Archivo no encontrado (expirado o no subido). Sube de nuevo."} return {"status": "failed", "error": "Archivo no encontrado (expirado o no subido). Sube de nuevo."}
_ensure_worker_has_meta_from_redis(job_id, file_path) _ensure_worker_has_meta_from_redis(job_id, file_path)
error_dir = os.path.join(os.path.dirname(file_path).replace("temp", "errors"), "") error_dir = layout_path("imports", "errors")
os.makedirs(error_dir, exist_ok=True) os.makedirs(error_dir, exist_ok=True)
error_path = os.path.join(error_dir, f"veh_{job_id}.jsonl") error_path = os.path.join(error_dir, f"veh_{job_id}.jsonl")
@@ -404,8 +405,7 @@ def _do_commit(job_id: str) -> Dict[str, Any]:
else: else:
_ensure_worker_has_meta_from_redis(job_id, file_path) _ensure_worker_has_meta_from_redis(job_id, file_path)
base_dir = os.path.dirname(file_path) error_dir = layout_path("imports", "errors")
error_dir = base_dir.replace("temp", "errors")
error_path = os.path.join(error_dir, f"veh_{job_id}.jsonl") error_path = os.path.join(error_dir, f"veh_{job_id}.jsonl")
error_lines = set() error_lines = set()

View File

@@ -8,7 +8,7 @@ from api.v1.common.tenant_crud_routes import TenantCRUDRoutes
from .dto import PartCreateDTO, PartResponseDTO, PartUpdateDTO from .dto import PartCreateDTO, PartResponseDTO, PartUpdateDTO
from .service import PartService from .service import PartService
from .imports.routes import router as imports_router from api.v1.modules.a76.layouts_csv.parts.routes import router as imports_router
router = APIRouter() router = APIRouter()

View File

@@ -31,7 +31,7 @@ from .routes.pedimento_rectification_origin import (
from .routes.pedimento_transport_means import router as pedimento_transport_means_router from .routes.pedimento_transport_means import router as pedimento_transport_means_router
from .routes.pedimento_validation import router as pedimento_validation_router from .routes.pedimento_validation import router as pedimento_validation_router
from .routes.pedimentos import router as pedimentos_router from .routes.pedimentos import router as pedimentos_router
from .imports.routes import router as pedimentos_imports_router from api.v1.modules.a76.layouts_csv.pedmientos.routes import router as pedimentos_imports_router
router = APIRouter() router = APIRouter()

View File

@@ -14,7 +14,7 @@ from .items.routes import router as items_router
from .classes import router as classes_router from .classes import router as classes_router
from .clients_and_providers import router as client_and_provider_router from .clients_and_providers import router as client_and_provider_router
from .imports.routes import router as imports_router from .layouts_csv.facturas.routes import router as imports_router
from .csv_templates.routes import router as csv_templates_router from .csv_templates.routes import router as csv_templates_router
from .invoice_settings.routes import router as invoice_settings_router from .invoice_settings.routes import router as invoice_settings_router
from .item_presets.routes import router as item_presets_router from .item_presets.routes import router as item_presets_router

View File

@@ -8,7 +8,7 @@ from sqlalchemy.orm import Session
from .dto import DriverCreateDTO, DriverResponseDTO from .dto import DriverCreateDTO, DriverResponseDTO
from .models import Driver from .models import Driver
from .services import DriverService from .services import DriverService
from .imports.routes import router as imports_router from api.v1.modules.a76.layouts_csv.drivers.routes import router as imports_router
router = APIRouter(prefix="/drivers") router = APIRouter(prefix="/drivers")

View File

@@ -4,7 +4,7 @@ from api.v1.common.tenant_crud_routes import TenantCRUDRoutes
from .dto import TrailerCreateDTO, TrailerResponseDTO, TrailerUpdateDTO from .dto import TrailerCreateDTO, TrailerResponseDTO, TrailerUpdateDTO
from .services import TrailerService from .services import TrailerService
from .imports.routes import router as imports_router from api.v1.modules.a76.layouts_csv.trailers.routes import router as imports_router
# Main router: trailers CRUD + CSV imports # Main router: trailers CRUD + CSV imports
router = APIRouter() router = APIRouter()

View File

@@ -4,7 +4,7 @@ from api.v1.common.tenant_crud_routes import TenantCRUDRoutes
from .dto import VehicleCreateDTO, VehicleResponseDTO, VehicleUpdateDTO from .dto import VehicleCreateDTO, VehicleResponseDTO, VehicleUpdateDTO
from .services import VehicleService from .services import VehicleService
from .imports.routes import router as imports_router from api.v1.modules.a76.layouts_csv.vehicles.routes import router as imports_router
# Main router: vehicles CRUD + CSV imports # Main router: vehicles CRUD + CSV imports
router = APIRouter() router = APIRouter()

Binary file not shown.

View File

@@ -32,16 +32,18 @@ celery_app.conf.update(
"api.v1.modules.a76.reports.exportacion.aviso_consolidado.task", "api.v1.modules.a76.reports.exportacion.aviso_consolidado.task",
"api.v1.modules.a76.reports.movements.invoices.tasks", "api.v1.modules.a76.reports.movements.invoices.tasks",
"api.v1.modules.a76.reports.exportacion.descargo.task", "api.v1.modules.a76.reports.exportacion.descargo.task",
"api.v1.modules.a76.imports.tasks", "api.v1.modules.a76.layouts_csv.facturas.tasks",
"api.v1.modules.a76.customs_brokers.imports.tasks", "api.v1.modules.a76.layouts_csv.customs_brokers.tasks",
"api.v1.modules.a76.clients_and_providers.imports.tasks", "api.v1.modules.a76.layouts_csv.clients_and_providers.tasks",
"api.v1.modules.a76.pedmientos.imports.tasks", "api.v1.modules.a76.layouts_csv.pedmientos.tasks",
"api.v1.modules.a76.general_catalogs.exchange_rate.imports.tasks", "api.v1.modules.a76.layouts_csv.exchange_rate.tasks",
"api.v1.modules.a76.general_catalogs.fractions.us_tariff_fractions.imports.tasks", "api.v1.modules.a76.layouts_csv.us_tariff_fractions.tasks",
"api.v1.modules.a76.classes.imports.tasks", "api.v1.modules.a76.layouts_csv.classes.tasks",
"api.v1.modules.a76.parts.imports.tasks", "api.v1.modules.a76.layouts_csv.parts.tasks",
"api.v1.modules.a76.boms.imports.tasks", "api.v1.modules.a76.layouts_csv.boms.tasks",
"api.v1.modules.a76.transportation.vehicles.imports.tasks", "api.v1.modules.a76.layouts_csv.vehicles.tasks",
"api.v1.modules.a76.layouts_csv.drivers.tasks",
"api.v1.modules.a76.layouts_csv.trailers.tasks",
"api.v1.modules.a76.reports.exportacion.transmission.MAINX30.task", "api.v1.modules.a76.reports.exportacion.transmission.MAINX30.task",
"api.v1.modules.a76.reports.importacion.transmission.temporal.MAINX30.task", "api.v1.modules.a76.reports.importacion.transmission.temporal.MAINX30.task",
"api.v1.modules.a76.reports.importacion.transmission.definitive.MAINX30.task", "api.v1.modules.a76.reports.importacion.transmission.definitive.MAINX30.task",

15
backend/core/paths.py Normal file
View File

@@ -0,0 +1,15 @@
"""
Rutas base y resolución de paths para layouts (importación CSV, temp, errors).
"""
from pathlib import Path
# Raíz del backend (directorio que contiene api/, core/, etc.)
BASE_DIR = Path(__file__).resolve().parent.parent
def layout_path(*parts: str) -> str:
"""Construye una ruta absoluta bajo backend/layouts/."""
p = BASE_DIR / "layouts"
for part in parts:
p = p / part
return str(p)

View File

@@ -89,6 +89,7 @@ import core.celery_app # Initialize Celery App
from api.v1.router import router as api_v1_router from api.v1.router import router as api_v1_router
from core.config import settings from core.config import settings
from core.database import init_db from core.database import init_db
from core.paths import layout_path
from core.error_handlers import register_exception_handlers from core.error_handlers import register_exception_handlers
from core.middleware import ( from core.middleware import (
LicenseValidationMiddleware, LicenseValidationMiddleware,
@@ -359,6 +360,9 @@ uploads_dir = Path("uploads").resolve()
uploads_dir.mkdir(parents=True, exist_ok=True) uploads_dir.mkdir(parents=True, exist_ok=True)
app.mount("/api/uploads", StaticFiles(directory=str(uploads_dir)), name="uploads") app.mount("/api/uploads", StaticFiles(directory=str(uploads_dir)), name="uploads")
# Directorios para importación CSV (layouts: temp y errors)
Path(layout_path("imports", "temp")).mkdir(parents=True, exist_ok=True)
Path(layout_path("imports", "errors")).mkdir(parents=True, exist_ok=True)
# Registrar routers # Registrar routers
app.include_router(api_v1_router, prefix="/api/v1") app.include_router(api_v1_router, prefix="/api/v1")