Compare commits
2 Commits
e378f2d949
...
b1df613651
| Author | SHA1 | Date | |
|---|---|---|---|
| b1df613651 | |||
| 94846fec8a |
@@ -9,7 +9,7 @@ class CustomUserSerializer(serializers.ModelSerializer):
|
||||
Serializer for the CustomUser model.
|
||||
"""
|
||||
|
||||
password = serializers.CharField(write_only=True)
|
||||
password = serializers.CharField(write_only=True, required=False)
|
||||
groups = serializers.PrimaryKeyRelatedField(queryset=Group.objects.all(), many=True, required=False)
|
||||
rfc = serializers.PrimaryKeyRelatedField(
|
||||
queryset=Importador.objects.all(),
|
||||
@@ -23,6 +23,17 @@ class CustomUserSerializer(serializers.ModelSerializer):
|
||||
fields = ['id', 'username', 'email', 'first_name', 'last_name', 'password', 'profile_picture', 'organizacion', 'is_importador', 'rfc', 'is_active', 'is_superuser', 'groups']
|
||||
read_only_fields = ['id', 'organizacion', 'is_superuser']
|
||||
|
||||
def validate_password(self, value):
|
||||
if not value or not value.strip():
|
||||
raise serializers.ValidationError("La contraseña no puede estar vacía o contener solo espacios.")
|
||||
return value
|
||||
|
||||
def validate(self, attrs):
|
||||
# En create, la contraseña es obligatoria
|
||||
if self.instance is None and not attrs.get('password'):
|
||||
raise serializers.ValidationError({"password": "Este campo es requerido."})
|
||||
return attrs
|
||||
|
||||
def create(self, validated_data):
|
||||
groups = validated_data.pop('groups', [])
|
||||
rfcs = validated_data.pop('rfc', [])
|
||||
|
||||
@@ -184,7 +184,7 @@ class EDocumentSerializer(serializers.ModelSerializer):
|
||||
numero = str(obj.numero_edocument).strip()
|
||||
# id_pedimento = str(obj.pedimento_id).strip()
|
||||
|
||||
# excluir e documents de tipo request y de tipo error
|
||||
# excluir solo request (21, 25); errores (22, 26) se incluyen para detección en frontend
|
||||
qs = Document.objects.filter(
|
||||
pedimento=obj.pedimento,
|
||||
archivo__icontains=numero,
|
||||
@@ -240,15 +240,11 @@ class CoveSerializer(serializers.ModelSerializer):
|
||||
try:
|
||||
numero = str(obj.numero_cove).strip()
|
||||
|
||||
# Excluir los tipo de documento 20, 24, 23 y 19
|
||||
# 20 = error solicitud cove
|
||||
# 24 = error solicitud acuse cove
|
||||
# 23 = request acuse cove
|
||||
# 19 = request cove
|
||||
# Excluir solo request (19, 23); errores (20, 24) se incluyen para detección en frontend
|
||||
qs = Document.objects.filter(
|
||||
pedimento=obj.pedimento,
|
||||
archivo__icontains=numero,
|
||||
).exclude(document_type_id__in=[20, 24, 23, 19])
|
||||
).exclude(document_type_id__in=[19, 23])
|
||||
|
||||
# Filtro por organización si aplica
|
||||
if hasattr(obj, 'organizacion') and obj.organizacion:
|
||||
|
||||
@@ -91,12 +91,18 @@ def procesar_coves_pedimento(pedimento_id):
|
||||
"credencial": credenciales_dict
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
f"{SERVICE_API_URL_V2}/services/all/coves",
|
||||
data=json.dumps(payload),
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
print(f"Servicio de COVEs enviado para pedimento {pedimento.pedimento}")
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{SERVICE_API_URL_V2}/services/all/coves",
|
||||
data=json.dumps(payload),
|
||||
headers={"Content-Type": "application/json"},
|
||||
timeout=60
|
||||
)
|
||||
response.raise_for_status()
|
||||
logging.info(f"COVEs encolados para pedimento {pedimento.pedimento}: {response.json().get('total', '?')}")
|
||||
except requests.exceptions.RequestException as e:
|
||||
logging.error(f"Error encolando COVEs para pedimento {pedimento.pedimento}: {e}")
|
||||
raise
|
||||
|
||||
@shared_task
|
||||
def procesar_acuse_coves_pedimento(pedimento_id):
|
||||
@@ -107,19 +113,25 @@ def procesar_acuse_coves_pedimento(pedimento_id):
|
||||
id=CredencialesImportador.objects.filter(rfc=pedimento.contribuyente).first().vucem.id
|
||||
).first()
|
||||
credenciales_dict = credenciales_to_dict(credenciales)
|
||||
|
||||
|
||||
payload = {
|
||||
"coves": [cove_to_dict(cove) for cove in pedimento.coves.filter(acuse_cove_descargado=False)],
|
||||
"pedimento": pedimento_dict,
|
||||
"credencial": credenciales_dict
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
f"{SERVICE_API_URL_V2}/services/all/acuse/cove/",
|
||||
data=json.dumps(payload),
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
print(f"Servicio de acuses de COVEs enviado para pedimento {pedimento.pedimento}")
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{SERVICE_API_URL_V2}/services/all/acuse/cove/",
|
||||
data=json.dumps(payload),
|
||||
headers={"Content-Type": "application/json"},
|
||||
timeout=60
|
||||
)
|
||||
response.raise_for_status()
|
||||
logging.info(f"Acuses de COVEs encolados para pedimento {pedimento.pedimento}: {response.json().get('total', '?')}")
|
||||
except requests.exceptions.RequestException as e:
|
||||
logging.error(f"Error encolando acuses de COVEs para pedimento {pedimento.pedimento}: {e}")
|
||||
raise
|
||||
|
||||
@shared_task
|
||||
def procesar_edocs_pedimento(pedimento_id):
|
||||
@@ -130,19 +142,25 @@ def procesar_edocs_pedimento(pedimento_id):
|
||||
id=CredencialesImportador.objects.filter(rfc=pedimento.contribuyente).first().vucem.id
|
||||
).first()
|
||||
credenciales_dict = credenciales_to_dict(credenciales)
|
||||
|
||||
|
||||
payload = {
|
||||
"edocs": [edoc_to_dict(edoc) for edoc in pedimento.documentos.filter(edocument_descargado=False)],
|
||||
"pedimento": pedimento_dict,
|
||||
"credencial": credenciales_dict
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
f"{SERVICE_API_URL_V2}/services/download/all/edocs/",
|
||||
data=json.dumps(payload),
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
print(f"Servicio de E-documents enviado para pedimento {pedimento.pedimento}")
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{SERVICE_API_URL_V2}/services/download/all/edocs/",
|
||||
data=json.dumps(payload),
|
||||
headers={"Content-Type": "application/json"},
|
||||
timeout=60
|
||||
)
|
||||
response.raise_for_status()
|
||||
logging.info(f"E-documents encolados para pedimento {pedimento.pedimento}: {response.json().get('total', '?')}")
|
||||
except requests.exceptions.RequestException as e:
|
||||
logging.error(f"Error encolando E-documents para pedimento {pedimento.pedimento}: {e}")
|
||||
raise
|
||||
|
||||
@shared_task
|
||||
def procesar_acuses_pedimento(pedimento_id):
|
||||
@@ -153,19 +171,25 @@ def procesar_acuses_pedimento(pedimento_id):
|
||||
id=CredencialesImportador.objects.filter(rfc=pedimento.contribuyente).first().vucem.id
|
||||
).first()
|
||||
credenciales_dict = credenciales_to_dict(credenciales)
|
||||
|
||||
|
||||
payload = {
|
||||
"edocs": [edoc_to_dict(edoc) for edoc in pedimento.documentos.filter(acuse_descargado=False)],
|
||||
"pedimento": pedimento_dict,
|
||||
"credencial": credenciales_dict
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
f"{SERVICE_API_URL_V2}/services/all/acuse/pedimento/",
|
||||
data=json.dumps(payload),
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
print(f"Servicio de acuses enviado para pedimento {pedimento.pedimento}")
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{SERVICE_API_URL_V2}/services/all/acuse/pedimento/",
|
||||
data=json.dumps(payload),
|
||||
headers={"Content-Type": "application/json"},
|
||||
timeout=60
|
||||
)
|
||||
response.raise_for_status()
|
||||
logging.info(f"Acuses encolados para pedimento {pedimento.pedimento}: {response.json().get('total', '?')}")
|
||||
except requests.exceptions.RequestException as e:
|
||||
logging.error(f"Error encolando acuses para pedimento {pedimento.pedimento}: {e}")
|
||||
raise
|
||||
|
||||
@shared_task
|
||||
def procesar_partidas_pedimento(pedimento_id):
|
||||
@@ -176,19 +200,32 @@ def procesar_partidas_pedimento(pedimento_id):
|
||||
id=CredencialesImportador.objects.filter(rfc=pedimento.contribuyente).first().vucem.id
|
||||
).first()
|
||||
credenciales_dict = credenciales_to_dict(credenciales)
|
||||
|
||||
|
||||
partidas_pendientes = list(pedimento.partidas.filter(descargado=False))
|
||||
payload = {
|
||||
"partidas": [partida_to_dict(partida) for partida in pedimento.partidas.filter(descargado=False)],
|
||||
"partidas": [partida_to_dict(p) for p in partidas_pendientes],
|
||||
"pedimento": pedimento_dict,
|
||||
"credencial": credenciales_dict
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
f"{SERVICE_API_URL_V2}/services/all/partidas/",
|
||||
data=json.dumps(payload),
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
print(f"Servicio de partidas enviado para pedimento {pedimento.pedimento}")
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{SERVICE_API_URL_V2}/services/all/partidas/",
|
||||
data=json.dumps(payload),
|
||||
headers={"Content-Type": "application/json"},
|
||||
timeout=60
|
||||
)
|
||||
response.raise_for_status()
|
||||
result = response.json()
|
||||
logging.info(
|
||||
f"Partidas encoladas para pedimento {pedimento.pedimento}: "
|
||||
f"{result.get('total', 0)} de {len(partidas_pendientes)}"
|
||||
)
|
||||
except requests.exceptions.RequestException as e:
|
||||
logging.error(
|
||||
f"Error encolando partidas para pedimento {pedimento.pedimento}: {e}"
|
||||
)
|
||||
raise
|
||||
|
||||
@shared_task
|
||||
def procesar_remesas_pedimento(pedimento_id):
|
||||
@@ -205,12 +242,18 @@ def procesar_remesas_pedimento(pedimento_id):
|
||||
"credencial": credenciales_dict
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
f"{SERVICE_API_URL_V2}/services/remesas",
|
||||
data=json.dumps(payload),
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
print(f"Servicio de remesas enviado para pedimento {pedimento.pedimento}")
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{SERVICE_API_URL_V2}/services/remesas",
|
||||
data=json.dumps(payload),
|
||||
headers={"Content-Type": "application/json"},
|
||||
timeout=60
|
||||
)
|
||||
response.raise_for_status()
|
||||
logging.info(f"Remesa encolada para pedimento {pedimento.pedimento}")
|
||||
except requests.exceptions.RequestException as e:
|
||||
logging.error(f"Error encolando remesa para pedimento {pedimento.pedimento}: {e}")
|
||||
raise
|
||||
|
||||
@shared_task
|
||||
def procesar_pedimento_completo_individual(pedimento_id):
|
||||
@@ -225,13 +268,19 @@ def procesar_pedimento_completo_individual(pedimento_id):
|
||||
"pedimento": pedimento_dict,
|
||||
"credencial": credenciales_dict
|
||||
}
|
||||
response = requests.post(
|
||||
f"{SERVICE_API_URL_V2}/services/pedimento_completo",
|
||||
data=json.dumps(payload),
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
print(f"Servicio enviado para pedimento {pedimento.pedimento}")
|
||||
return response
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{SERVICE_API_URL_V2}/services/pedimento_completo",
|
||||
data=json.dumps(payload),
|
||||
headers={"Content-Type": "application/json"},
|
||||
timeout=60
|
||||
)
|
||||
response.raise_for_status()
|
||||
logging.info(f"Pedimento completo encolado: {pedimento.pedimento}")
|
||||
return response
|
||||
except requests.exceptions.RequestException as e:
|
||||
logging.error(f"Error encolando pedimento completo {pedimento.pedimento}: {e}")
|
||||
raise
|
||||
|
||||
@shared_task
|
||||
def procesar_pedimentos_completos(organizacion_id):
|
||||
@@ -270,13 +319,18 @@ def procesar_pedimentos_completos(organizacion_id):
|
||||
url = f"{SERVICE_API_URL_V2}/services/pedimento_completo"
|
||||
dataJson = json.dumps(payload)
|
||||
|
||||
response = requests.post(
|
||||
url,
|
||||
data=dataJson,
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
# Aquí puedes continuar con el resto de tu lógica
|
||||
print(f"Servicio enviado para pedimento {pedimento.pedimento}")
|
||||
try:
|
||||
response = requests.post(
|
||||
url,
|
||||
data=dataJson,
|
||||
headers={"Content-Type": "application/json"},
|
||||
timeout=60
|
||||
)
|
||||
response.raise_for_status()
|
||||
logging.info(f"Pedimento completo encolado: {pedimento.pedimento}")
|
||||
except requests.exceptions.RequestException as e:
|
||||
logging.error(f"Error encolando pedimento completo {pedimento.pedimento}: {e}")
|
||||
continue
|
||||
|
||||
@shared_task
|
||||
def procesar_remesas(organizacion_id):
|
||||
@@ -311,9 +365,11 @@ def procesar_remesas(organizacion_id):
|
||||
response = requests.post(
|
||||
f"{SERVICE_API_URL_V2}/services/remesas/",
|
||||
data=json.dumps(payload),
|
||||
headers={"Content-Type": "application/json"}
|
||||
headers={"Content-Type": "application/json"},
|
||||
timeout=60
|
||||
)
|
||||
logger.info(f"Servicio enviado para pedimento {pedimento.pedimento} — status {response.status_code}")
|
||||
response.raise_for_status()
|
||||
logger.info(f"Remesa encolada para pedimento {pedimento.pedimento} — status {response.status_code}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error procesando remesa para pedimento {pedimento.pedimento}: {e}", exc_info=True)
|
||||
@@ -339,14 +395,18 @@ def procesar_coves(organizacion_id):
|
||||
"credencial": credenciales_dict
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
f"{SERVICE_API_URL_V2}/services/all/coves",
|
||||
data=json.dumps(payload),
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
# Aquí puedes continuar con el resto de tu lógica
|
||||
|
||||
print(f"Servicio enviado para pedimento {pedimento.pedimento}")
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{SERVICE_API_URL_V2}/services/all/coves",
|
||||
data=json.dumps(payload),
|
||||
headers={"Content-Type": "application/json"},
|
||||
timeout=60
|
||||
)
|
||||
response.raise_for_status()
|
||||
logging.info(f"COVEs encolados para pedimento {pedimento.pedimento}: {response.json().get('total', '?')}")
|
||||
except requests.exceptions.RequestException as e:
|
||||
logging.error(f"Error encolando COVEs para pedimento {pedimento.pedimento}: {e}")
|
||||
continue
|
||||
|
||||
@shared_task
|
||||
def procesar_acuse_coves(organizacion_id):
|
||||
@@ -370,14 +430,18 @@ def procesar_acuse_coves(organizacion_id):
|
||||
"credencial": credenciales_dict
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
f"{SERVICE_API_URL_V2}/services/all/acuse/cove/",
|
||||
data=json.dumps(payload),
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
# Aquí puedes continuar con el resto de tu lógica
|
||||
|
||||
print(f"Servicio enviado para pedimento {pedimento.pedimento}")
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{SERVICE_API_URL_V2}/services/all/acuse/cove/",
|
||||
data=json.dumps(payload),
|
||||
headers={"Content-Type": "application/json"},
|
||||
timeout=60
|
||||
)
|
||||
response.raise_for_status()
|
||||
logging.info(f"Acuses de COVEs encolados para pedimento {pedimento.pedimento}: {response.json().get('total', '?')}")
|
||||
except requests.exceptions.RequestException as e:
|
||||
logging.error(f"Error encolando acuses de COVEs para pedimento {pedimento.pedimento}: {e}")
|
||||
continue
|
||||
|
||||
@shared_task
|
||||
def procesar_acuses(organizacion_id):
|
||||
@@ -401,14 +465,18 @@ def procesar_acuses(organizacion_id):
|
||||
"credencial": credenciales_dict
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
f"{SERVICE_API_URL_V2}/services/all/acuse/pedimento/",
|
||||
data=json.dumps(payload),
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
# Aquí puedes continuar con el resto de tu lógica
|
||||
|
||||
print(f"Servicio enviado para pedimento {pedimento.pedimento}")
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{SERVICE_API_URL_V2}/services/all/acuse/pedimento/",
|
||||
data=json.dumps(payload),
|
||||
headers={"Content-Type": "application/json"},
|
||||
timeout=60
|
||||
)
|
||||
response.raise_for_status()
|
||||
logging.info(f"Acuses encolados para pedimento {pedimento.pedimento}: {response.json().get('total', '?')}")
|
||||
except requests.exceptions.RequestException as e:
|
||||
logging.error(f"Error encolando acuses para pedimento {pedimento.pedimento}: {e}")
|
||||
continue
|
||||
|
||||
@shared_task
|
||||
def procesar_edocs(organizacion_id):
|
||||
@@ -432,14 +500,18 @@ def procesar_edocs(organizacion_id):
|
||||
"credencial": credenciales_dict
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
f"{SERVICE_API_URL_V2}/services/download/all/edocs/",
|
||||
data=json.dumps(payload),
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
# Aquí puedes continuar con el resto de tu lógica
|
||||
|
||||
print(f"Servicio enviado para pedimento {pedimento.pedimento}")
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{SERVICE_API_URL_V2}/services/download/all/edocs/",
|
||||
data=json.dumps(payload),
|
||||
headers={"Content-Type": "application/json"},
|
||||
timeout=60
|
||||
)
|
||||
response.raise_for_status()
|
||||
logging.info(f"E-documents encolados para pedimento {pedimento.pedimento}: {response.json().get('total', '?')}")
|
||||
except requests.exceptions.RequestException as e:
|
||||
logging.error(f"Error encolando E-documents para pedimento {pedimento.pedimento}: {e}")
|
||||
continue
|
||||
|
||||
@shared_task
|
||||
def procesar_partidas(organizacion_id):
|
||||
@@ -447,29 +519,42 @@ def procesar_partidas(organizacion_id):
|
||||
organizacion_id=organizacion_id,
|
||||
partidas__isnull=False
|
||||
).distinct()
|
||||
|
||||
|
||||
for pedimento in pedimentos:
|
||||
if pedimento.partidas.filter(descargado=False).exists(): # Tipo 4: Partidas
|
||||
# Convertir el pedimento a JSON usando el serializer
|
||||
pedimento_dict = pedimento_to_dict(pedimento)
|
||||
credenciales = Vucem.objects.filter(id=CredencialesImportador.objects.filter(rfc=pedimento.contribuyente).first().vucem.id).first()
|
||||
partidas_pendientes = list(pedimento.partidas.filter(descargado=False))
|
||||
if not partidas_pendientes:
|
||||
continue
|
||||
|
||||
credenciales_dict = credenciales_to_dict(credenciales)
|
||||
|
||||
payload = {
|
||||
"partidas": [partida_to_dict(partida) for partida in pedimento.partidas.filter(descargado=False)],
|
||||
"pedimento": pedimento_dict,
|
||||
"credencial": credenciales_dict
|
||||
}
|
||||
pedimento_dict = pedimento_to_dict(pedimento)
|
||||
credenciales = Vucem.objects.filter(
|
||||
id=CredencialesImportador.objects.filter(rfc=pedimento.contribuyente).first().vucem.id
|
||||
).first()
|
||||
credenciales_dict = credenciales_to_dict(credenciales)
|
||||
|
||||
payload = {
|
||||
"partidas": [partida_to_dict(p) for p in partidas_pendientes],
|
||||
"pedimento": pedimento_dict,
|
||||
"credencial": credenciales_dict
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{SERVICE_API_URL_V2}/services/all/partidas/",
|
||||
f"{SERVICE_API_URL_V2}/services/all/partidas/",
|
||||
data=json.dumps(payload),
|
||||
headers={"Content-Type": "application/json"}
|
||||
headers={"Content-Type": "application/json"},
|
||||
timeout=60
|
||||
)
|
||||
# Aquí puedes continuar con el resto de tu lógica
|
||||
|
||||
print(f"Servicio enviado para pedimento {pedimento.pedimento}")
|
||||
response.raise_for_status()
|
||||
result = response.json()
|
||||
logging.info(
|
||||
f"Partidas encoladas para pedimento {pedimento.pedimento}: "
|
||||
f"{result.get('total', 0)} de {len(partidas_pendientes)}"
|
||||
)
|
||||
except requests.exceptions.RequestException as e:
|
||||
logging.error(
|
||||
f"Error encolando partidas para pedimento {pedimento.pedimento}: {e}"
|
||||
)
|
||||
continue
|
||||
|
||||
@shared_task
|
||||
def documentos_con_errores(organizacion_id):
|
||||
|
||||
@@ -62,6 +62,7 @@ from .views_auditor import (
|
||||
auditor_obtener_peticion_edocument_vu,
|
||||
auditor_obtener_respuesta_edocument_vu,
|
||||
auditar_pedimento_endpoint,
|
||||
procesar_pedimento_completo_endpoint,
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
@@ -80,6 +81,7 @@ urlpatterns = [
|
||||
path('auditor/auditar-acuse/pedimento/', auditar_acuse_pedimento_endpoint, name='auditar-acuse-pedimento'),
|
||||
path('auditor/auditar-remesa/pedimento/', auditar_procesamiento_remesa_pedimento_endpoint, name='auditar-remesa-pedimento'),
|
||||
path('auditor/auditar-pedimento/', auditar_pedimento_endpoint, name='auditar-pedimento'),
|
||||
path('auditor/procesar-pedimento-completo/pedimento/', procesar_pedimento_completo_endpoint, name='procesar-pedimento-completo-pedimento'),
|
||||
|
||||
path('auditor/procesar-pedimentos/organizaciones/', auditor_procesar_pedimentos_organizacion, name='procesar-pedimentos-organizaciones'),
|
||||
path('auditor/peticion-respuesta/pedimento-vu/', auditar_peticion_respuesta_pedimento_completo, name='peticion-respuesta-pedimento-vu'),
|
||||
|
||||
@@ -2505,6 +2505,7 @@ class ViewSetEDocument(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
|
||||
'partial_update': 'edocuments.edit',
|
||||
'destroy': 'edocuments.delete',
|
||||
'bulk_delete_edocs_vu': 'edocuments.delete',
|
||||
'reset_acuse': 'edocuments.edit',
|
||||
}
|
||||
codename = perms.get(self.action, 'edocuments.view')
|
||||
return [IsAuthenticated(), require_permission(codename)()]
|
||||
@@ -2531,6 +2532,88 @@ class ViewSetEDocument(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
|
||||
def perform_destroy(self, instance):
|
||||
instance.delete()
|
||||
|
||||
@action(detail=True, methods=['post'], url_path='reset-acuse')
|
||||
def reset_acuse(self, request, pk=None):
|
||||
"""
|
||||
Detecta inconsistencia cuando acuse_descargado=True pero no existe el documento
|
||||
de acuse (tipo 4). Crea un registro de error tipo 26 para Errores VU y
|
||||
restablece acuse_descargado=False para permitir reintentar.
|
||||
"""
|
||||
from api.record.models import Document, DocumentType
|
||||
import logging
|
||||
logger = logging.getLogger('api.customs.views')
|
||||
|
||||
edoc = self.get_object()
|
||||
|
||||
if not edoc.acuse_descargado:
|
||||
return Response(
|
||||
{"error": "El acuse no está marcado como descargado"},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
# Verificar si el acuse PDF (tipo 4 = Pedimento Acuse) existe realmente
|
||||
acuse_disponible = Document.objects.filter(
|
||||
pedimento=edoc.pedimento,
|
||||
archivo__icontains=edoc.numero_edocument,
|
||||
document_type_id=4
|
||||
).exists()
|
||||
|
||||
if acuse_disponible:
|
||||
return Response(
|
||||
{"status": "El acuse está disponible correctamente", "acuse_disponible": True},
|
||||
status=status.HTTP_200_OK
|
||||
)
|
||||
|
||||
# Inconsistencia confirmada: crear documento de error tipo 26 para Errores VU
|
||||
doc_type_error = DocumentType.objects.filter(id=26).first()
|
||||
if doc_type_error:
|
||||
error_content = (
|
||||
f"Inconsistencia detectada: el acuse del EDocument {edoc.numero_edocument} "
|
||||
f"fue marcado como descargado pero el documento no se encuentra disponible. "
|
||||
f"El estado fue restablecido para permitir reprocesamiento."
|
||||
).encode('utf-8')
|
||||
|
||||
try:
|
||||
with tempfile.NamedTemporaryFile(
|
||||
mode='wb', suffix='.txt', delete=False
|
||||
) as f:
|
||||
f.write(error_content)
|
||||
tmp_path = f.name
|
||||
|
||||
pedimento_app = getattr(edoc.pedimento, 'pedimento_app', str(edoc.pedimento.pedimento))
|
||||
file_name = f"error_acuse_{edoc.numero_edocument}.txt"
|
||||
|
||||
saved_path = storage_service.save_document_from_path(
|
||||
file_path=tmp_path,
|
||||
file_name=file_name,
|
||||
organizacion_id=edoc.organizacion_id,
|
||||
pedimento_app=pedimento_app
|
||||
)
|
||||
|
||||
if saved_path:
|
||||
Document.objects.create(
|
||||
organizacion=edoc.organizacion,
|
||||
pedimento=edoc.pedimento,
|
||||
archivo=saved_path,
|
||||
document_type=doc_type_error,
|
||||
extension='TXT',
|
||||
size=len(error_content),
|
||||
fuente=None,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Error creando documento de error para acuse {edoc.numero_edocument}: {e}"
|
||||
)
|
||||
finally:
|
||||
if os.path.exists(tmp_path):
|
||||
os.unlink(tmp_path)
|
||||
|
||||
edoc.acuse_descargado = False
|
||||
edoc.save()
|
||||
|
||||
serializer = self.get_serializer(edoc)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
class ViewSetCove(viewsets.ModelViewSet, OrganizacionFiltradaMixin):
|
||||
"""
|
||||
ViewSet for Cove model.
|
||||
|
||||
@@ -15,7 +15,7 @@ from .tasks.auditoria import (
|
||||
auditar_remesas,
|
||||
)
|
||||
from .tasks.internal_services import auditar_pedimentos
|
||||
from .tasks.microservice_v2 import procesar_pedimentos_completos
|
||||
from .tasks.microservice_v2 import procesar_pedimentos_completos, procesar_pedimento_completo_individual
|
||||
from api.customs.models import Pedimento
|
||||
from api.organization.models import Organizacion
|
||||
from api.record.models import Document
|
||||
@@ -25,6 +25,9 @@ import os
|
||||
from api.utils.storage_service import storage_service
|
||||
import logging
|
||||
import uuid
|
||||
|
||||
_ERROR_DOCUMENT_TYPES = [10, 14, 16, 18, 20, 22, 24, 26]
|
||||
|
||||
logger = logging.getLogger('api.customs.views_auditor')
|
||||
|
||||
def get_document_content(documento):
|
||||
@@ -1680,98 +1683,155 @@ def auditor_obtener_respuesta_edocument_vu(request):
|
||||
@permission_classes([IsAuthenticated, require_permission('auditoria.process')])
|
||||
def auditar_pedimento_endpoint(request):
|
||||
"""
|
||||
Audita un pedimento específico verificando si existe su XML y extrayendo información.
|
||||
Audita el pedimento completo (PC): ¿está descargado? ¿se puede procesar?
|
||||
Incluye diagnóstico de campos y errores detectados por tipo de documento.
|
||||
"""
|
||||
pedimento_id = request.data.get('pedimento_id')
|
||||
|
||||
|
||||
if not pedimento_id:
|
||||
return Response(
|
||||
{'error': 'Debe proporcionar pedimento_id'},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
|
||||
try:
|
||||
# Validar permisos y existencia del pedimento
|
||||
pedimento = Pedimento.objects.get(id=pedimento_id)
|
||||
pedimento = Pedimento.objects.select_related(
|
||||
'organizacion', 'contribuyente'
|
||||
).get(id=pedimento_id)
|
||||
user = request.user
|
||||
|
||||
|
||||
if not user.is_superuser and str(pedimento.organizacion.id) != str(user.organizacion.id):
|
||||
return Response(
|
||||
{'error': 'No tiene permisos para este pedimento'},
|
||||
status=status.HTTP_403_FORBIDDEN
|
||||
)
|
||||
|
||||
# Buscar documentos XML del pedimento
|
||||
documentos_xml = Document.objects.filter(
|
||||
pedimento=pedimento,
|
||||
archivo__endswith='.xml',
|
||||
|
||||
# PC descargado (type 2)
|
||||
pc_descargado = pedimento.documents.filter(
|
||||
document_type_id=2,
|
||||
organizacion=pedimento.organizacion
|
||||
).exists()
|
||||
|
||||
# Fuente de carga
|
||||
fuente = 'datastage' if pedimento.consultar_vucem else 'manual'
|
||||
|
||||
# Diagnóstico de campos
|
||||
aduana = pedimento.aduana or ''
|
||||
patente = pedimento.patente or ''
|
||||
numero_pedimento = pedimento.pedimento or ''
|
||||
|
||||
aduana_valida = bool(aduana) and aduana.isdigit() and 2 <= len(aduana) <= 3
|
||||
patente_valida = bool(patente) and patente.isdigit() and len(patente) == 4
|
||||
pedimento_valido = bool(numero_pedimento) and numero_pedimento.isdigit() and len(numero_pedimento) >= 7
|
||||
numero_operacion_presente = bool(pedimento.numero_operacion)
|
||||
|
||||
from api.vucem.models import CredencialesImportador
|
||||
tiene_contribuyente = pedimento.contribuyente is not None
|
||||
tiene_credenciales = False
|
||||
credenciales_detalle = None
|
||||
if tiene_contribuyente:
|
||||
credencial = CredencialesImportador.objects.filter(rfc=pedimento.contribuyente).first()
|
||||
tiene_credenciales = bool(credencial and credencial.vucem)
|
||||
if credencial and not credencial.vucem:
|
||||
credenciales_detalle = 'Credencial encontrada pero sin cuenta VUCEM asociada'
|
||||
elif not credencial:
|
||||
credenciales_detalle = f'Sin credenciales VUCEM para RFC {pedimento.contribuyente.rfc}'
|
||||
|
||||
razones = []
|
||||
if not aduana_valida:
|
||||
razones.append(f'Aduana inválida o ausente (valor: "{aduana}")')
|
||||
if not patente_valida:
|
||||
razones.append(f'Patente inválida o ausente (valor: "{patente}")')
|
||||
if not pedimento_valido:
|
||||
razones.append(f'Número de pedimento inválido (valor: "{numero_pedimento}")')
|
||||
if not tiene_contribuyente:
|
||||
razones.append('Sin contribuyente asignado')
|
||||
elif not tiene_credenciales:
|
||||
razones.append(credenciales_detalle or 'Sin credenciales VUCEM')
|
||||
|
||||
puede_procesar = len(razones) == 0
|
||||
|
||||
datos = {
|
||||
'aduana': aduana or None,
|
||||
'patente': patente or None,
|
||||
'numero_pedimento': numero_pedimento or None,
|
||||
'numero_operacion': pedimento.numero_operacion,
|
||||
'contribuyente_rfc': pedimento.contribuyente.rfc if pedimento.contribuyente else None,
|
||||
'contribuyente_nombre': str(pedimento.contribuyente) if pedimento.contribuyente else None,
|
||||
}
|
||||
|
||||
validacion = {
|
||||
'aduana_valida': aduana_valida,
|
||||
'patente_valida': patente_valida,
|
||||
'pedimento_valido': pedimento_valido,
|
||||
'numero_operacion_presente': numero_operacion_presente,
|
||||
'tiene_contribuyente': tiene_contribuyente,
|
||||
'tiene_credenciales_vucem': tiene_credenciales,
|
||||
}
|
||||
|
||||
# Errores por tipo de documento
|
||||
docs_error = (
|
||||
Document.objects
|
||||
.filter(
|
||||
pedimento=pedimento,
|
||||
organizacion=pedimento.organizacion,
|
||||
document_type_id__in=_ERROR_DOCUMENT_TYPES,
|
||||
)
|
||||
.select_related('document_type')
|
||||
.order_by('document_type_id')
|
||||
)
|
||||
|
||||
if not documentos_xml.exists():
|
||||
return Response({
|
||||
'pedimento_id': str(pedimento_id),
|
||||
'pedimento': pedimento.pedimento,
|
||||
'pedimento_app': pedimento.pedimento_app,
|
||||
'archivos_xml_encontrados': 0,
|
||||
'mensaje': 'No se encontraron archivos XML para este pedimento',
|
||||
'auditoria_completa': False
|
||||
}, status=status.HTTP_200_OK)
|
||||
|
||||
# Lista para almacenar información de cada XML
|
||||
xmls_analizados = []
|
||||
informacion_extraida = []
|
||||
|
||||
for documento in documentos_xml:
|
||||
errores_detectados = [
|
||||
{
|
||||
'documento_id': str(doc.id),
|
||||
'nombre_archivo': os.path.basename(str(doc.archivo)),
|
||||
'tipo_error': doc.document_type.descripcion if doc.document_type else 'Error desconocido',
|
||||
'tipo_id': doc.document_type_id,
|
||||
}
|
||||
for doc in docs_error
|
||||
]
|
||||
|
||||
print(f"documento >>>> {documento}")
|
||||
logger.info(f"documento >>>> {documento}")
|
||||
# XML del PC si existe
|
||||
informacion_xml = None
|
||||
doc_pc = pedimento.documents.filter(
|
||||
document_type_id=2,
|
||||
organizacion=pedimento.organizacion,
|
||||
archivo__endswith='.xml',
|
||||
).first()
|
||||
if doc_pc:
|
||||
xml_content = get_document_content(doc_pc)
|
||||
if xml_content:
|
||||
info_pedimento = extraer_info_pedimento_xml(xml_content)
|
||||
if info_pedimento:
|
||||
informacion_xml = info_pedimento
|
||||
actualizar_info_pedimento(pedimento, info_pedimento)
|
||||
|
||||
try:
|
||||
xml_info = {
|
||||
'documento_id': str(documento.id),
|
||||
'nombre_archivo': os.path.basename(str(documento.archivo)),
|
||||
'tamanio': documento.size,
|
||||
'extension': documento.extension,
|
||||
'tipo_documento': documento.document_type.descripcion if documento.document_type else 'Desconocido'
|
||||
}
|
||||
|
||||
xml_content = get_document_content(documento)
|
||||
|
||||
if xml_content is None:
|
||||
xml_info['error_lectura'] = 'No se pudo descargar el archivo'
|
||||
else:
|
||||
info_pedimento = extraer_info_pedimento_xml(xml_content)
|
||||
|
||||
if info_pedimento:
|
||||
xml_info['informacion_extraida'] = info_pedimento
|
||||
informacion_extraida.append(info_pedimento)
|
||||
hay_pendientes = not pc_descargado
|
||||
hay_errores = bool(errores_detectados)
|
||||
|
||||
# Actualizar el pedimento con la información encontrada si es necesario
|
||||
actualizar_info_pedimento(pedimento, info_pedimento)
|
||||
|
||||
xmls_analizados.append(xml_info)
|
||||
|
||||
except Exception as e:
|
||||
xmls_analizados.append({
|
||||
'documento_id': str(documento.id),
|
||||
'nombre_archivo': os.path.basename(str(documento.archivo)),
|
||||
'error': f'Error procesando archivo: {str(e)}'
|
||||
})
|
||||
|
||||
response_data = {
|
||||
if hay_errores:
|
||||
estado = 'CON_ERRORES'
|
||||
elif hay_pendientes:
|
||||
estado = 'PENDIENTE'
|
||||
else:
|
||||
estado = 'COMPLETO'
|
||||
|
||||
return Response({
|
||||
'pedimento_id': str(pedimento_id),
|
||||
'pedimento': pedimento.pedimento,
|
||||
'pedimento_app': pedimento.pedimento_app,
|
||||
'archivos_xml_encontrados': len(xmls_analizados),
|
||||
'xmls_analizados': xmls_analizados,
|
||||
'informacion_extraida': informacion_extraida,
|
||||
'auditoria_completa': True,
|
||||
'mensaje': f'Auditoría completada para el pedimento {pedimento.pedimento}'
|
||||
}
|
||||
|
||||
return Response(response_data, status=status.HTTP_200_OK)
|
||||
|
||||
'estado': estado,
|
||||
'hay_pendientes': hay_pendientes,
|
||||
'hay_errores': hay_errores,
|
||||
'pc_descargado': pc_descargado,
|
||||
'puede_procesar': puede_procesar,
|
||||
'razones_no_puede_procesar': razones,
|
||||
'fuente': fuente,
|
||||
'datos': datos,
|
||||
'validacion': validacion,
|
||||
'errores_detectados': errores_detectados,
|
||||
'informacion_xml': informacion_xml,
|
||||
}, status=status.HTTP_200_OK)
|
||||
|
||||
except Pedimento.DoesNotExist:
|
||||
return Response(
|
||||
{'error': 'Pedimento no encontrado'},
|
||||
@@ -1783,6 +1843,164 @@ def auditar_pedimento_endpoint(request):
|
||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||
)
|
||||
|
||||
@swagger_auto_schema(
|
||||
method='post',
|
||||
operation_description="Procesa el pedimento completo (tipo 2) de un pedimento específico llamando al microservicio VUCEM.",
|
||||
request_body=openapi.Schema(
|
||||
type=openapi.TYPE_OBJECT,
|
||||
properties={
|
||||
'pedimento_id': openapi.Schema(type=openapi.TYPE_STRING, description='ID del pedimento'),
|
||||
},
|
||||
required=['pedimento_id']
|
||||
),
|
||||
responses={
|
||||
202: openapi.Response('Procesamiento encolado — usar task_id para consultar resultado'),
|
||||
200: openapi.Response('El pedimento ya tiene su documento completo descargado'),
|
||||
400: openapi.Response('Error en los parámetros o prerequisitos faltantes'),
|
||||
403: openapi.Response('No tiene permisos suficientes'),
|
||||
404: openapi.Response('Pedimento no encontrado'),
|
||||
}
|
||||
)
|
||||
@api_view(['POST'])
|
||||
@permission_classes([IsAuthenticated, require_permission('auditoria.process')])
|
||||
def procesar_pedimento_completo_endpoint(request):
|
||||
"""
|
||||
Diagnostica el pedimento completo y, si todo está en orden y aún no se ha
|
||||
descargado, encola la tarea de procesamiento.
|
||||
|
||||
Siempre devuelve diagnóstico completo: validación de campos, fuente, estado
|
||||
del PC y razones por las que no se puede procesar si aplica.
|
||||
"""
|
||||
pedimento_id = request.data.get('pedimento_id')
|
||||
|
||||
if not pedimento_id:
|
||||
return Response(
|
||||
{'error': 'Debe proporcionar pedimento_id'},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
try:
|
||||
pedimento = Pedimento.objects.select_related(
|
||||
'organizacion', 'contribuyente', 'tipo_operacion'
|
||||
).get(id=pedimento_id)
|
||||
except Pedimento.DoesNotExist:
|
||||
return Response({'error': 'Pedimento no encontrado'}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
user = request.user
|
||||
if not user.is_superuser and str(pedimento.organizacion.id) != str(user.organizacion.id):
|
||||
return Response({'error': 'No tiene permisos para este pedimento'}, status=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
# --- Diagnóstico de campos ---
|
||||
aduana = pedimento.aduana or ''
|
||||
patente = pedimento.patente or ''
|
||||
numero_pedimento = pedimento.pedimento or ''
|
||||
|
||||
aduana_valida = bool(aduana) and aduana.isdigit() and 2 <= len(aduana) <= 3
|
||||
patente_valida = bool(patente) and patente.isdigit() and len(patente) == 4
|
||||
pedimento_valido = bool(numero_pedimento) and numero_pedimento.isdigit() and len(numero_pedimento) >= 7
|
||||
numero_operacion_presente = bool(pedimento.numero_operacion)
|
||||
|
||||
# --- Fuente de carga ---
|
||||
# consultar_vucem=True indica que fue originado desde datastage
|
||||
fuente = 'datastage' if pedimento.consultar_vucem else 'manual'
|
||||
|
||||
# --- Estado del PC ---
|
||||
pc_descargado = pedimento.documents.filter(
|
||||
document_type_id=2,
|
||||
organizacion=pedimento.organizacion
|
||||
).exists()
|
||||
|
||||
# --- Credenciales VUCEM ---
|
||||
from api.vucem.models import CredencialesImportador
|
||||
tiene_contribuyente = pedimento.contribuyente is not None
|
||||
tiene_credenciales = False
|
||||
credenciales_detalle = None
|
||||
if tiene_contribuyente:
|
||||
credencial = CredencialesImportador.objects.filter(rfc=pedimento.contribuyente).first()
|
||||
tiene_credenciales = bool(credencial and credencial.vucem)
|
||||
if credencial and not credencial.vucem:
|
||||
credenciales_detalle = 'Credencial encontrada pero sin cuenta VUCEM asociada'
|
||||
elif not credencial:
|
||||
credenciales_detalle = f'Sin credenciales VUCEM para RFC {pedimento.contribuyente.rfc}'
|
||||
|
||||
# --- Puede procesar ---
|
||||
razones = []
|
||||
if not aduana_valida:
|
||||
razones.append(f'Aduana inválida o ausente (valor: "{aduana}")')
|
||||
if not patente_valida:
|
||||
razones.append(f'Patente inválida o ausente (valor: "{patente}")')
|
||||
if not pedimento_valido:
|
||||
razones.append(f'Número de pedimento inválido (valor: "{numero_pedimento}")')
|
||||
if not tiene_contribuyente:
|
||||
razones.append('Sin contribuyente asignado')
|
||||
elif not tiene_credenciales:
|
||||
razones.append(credenciales_detalle or 'Sin credenciales VUCEM')
|
||||
|
||||
puede_procesar = len(razones) == 0
|
||||
|
||||
datos = {
|
||||
'aduana': aduana or None,
|
||||
'patente': patente or None,
|
||||
'numero_pedimento': numero_pedimento or None,
|
||||
'numero_operacion': pedimento.numero_operacion,
|
||||
'regimen': pedimento.regimen,
|
||||
'clave_pedimento': pedimento.clave_pedimento,
|
||||
'fecha_pago': str(pedimento.fecha_pago) if pedimento.fecha_pago else None,
|
||||
'contribuyente_rfc': pedimento.contribuyente.rfc if pedimento.contribuyente else None,
|
||||
'contribuyente_nombre': str(pedimento.contribuyente) if pedimento.contribuyente else None,
|
||||
'remesas': pedimento.remesas,
|
||||
'numero_partidas': pedimento.numero_partidas,
|
||||
}
|
||||
|
||||
validacion = {
|
||||
'aduana_valida': aduana_valida,
|
||||
'patente_valida': patente_valida,
|
||||
'pedimento_valido': pedimento_valido,
|
||||
'numero_operacion_presente': numero_operacion_presente,
|
||||
'tiene_contribuyente': tiene_contribuyente,
|
||||
'tiene_credenciales_vucem': tiene_credenciales,
|
||||
'puede_procesar': puede_procesar,
|
||||
'razones_no_puede_procesar': razones,
|
||||
}
|
||||
|
||||
base_response = {
|
||||
'pedimento_id': str(pedimento_id),
|
||||
'pedimento': pedimento.pedimento,
|
||||
'pedimento_app': pedimento.pedimento_app,
|
||||
'fuente': fuente,
|
||||
'datos': datos,
|
||||
'validacion': validacion,
|
||||
'pc_descargado': pc_descargado,
|
||||
}
|
||||
|
||||
# Ya descargado — devolver diagnóstico sin encolar
|
||||
if pc_descargado:
|
||||
return Response({
|
||||
**base_response,
|
||||
'estado': 'ya_descargado',
|
||||
'mensaje': 'El pedimento completo ya fue descargado',
|
||||
}, status=status.HTTP_200_OK)
|
||||
|
||||
# No puede procesar — devolver diagnóstico con razones
|
||||
if not puede_procesar:
|
||||
return Response({
|
||||
**base_response,
|
||||
'estado': 'no_puede_procesar',
|
||||
'mensaje': 'El pedimento no cumple los requisitos para procesar',
|
||||
}, status=status.HTTP_200_OK)
|
||||
|
||||
# Todo en orden — encolar
|
||||
task = procesar_pedimento_completo_individual.delay(str(pedimento_id))
|
||||
logger.info(f"Procesamiento PC encolado: {pedimento.pedimento} (task={task.id})")
|
||||
|
||||
return Response({
|
||||
**base_response,
|
||||
'estado': 'encolado',
|
||||
'task_id': task.id,
|
||||
'mensaje': f'Procesamiento encolado para {pedimento.pedimento_app}',
|
||||
}, status=status.HTTP_202_ACCEPTED)
|
||||
|
||||
|
||||
def actualizar_info_pedimento(pedimento, info_xml):
|
||||
"""
|
||||
Actualiza la información del pedimento con los datos extraídos del XML.
|
||||
|
||||
Reference in New Issue
Block a user