Compare commits
3 Commits
feature/rb
...
b1df613651
| Author | SHA1 | Date | |
|---|---|---|---|
| b1df613651 | |||
| 94846fec8a | |||
| e378f2d949 |
@@ -9,7 +9,7 @@ class CustomUserSerializer(serializers.ModelSerializer):
|
|||||||
Serializer for the CustomUser model.
|
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)
|
groups = serializers.PrimaryKeyRelatedField(queryset=Group.objects.all(), many=True, required=False)
|
||||||
rfc = serializers.PrimaryKeyRelatedField(
|
rfc = serializers.PrimaryKeyRelatedField(
|
||||||
queryset=Importador.objects.all(),
|
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']
|
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']
|
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):
|
def create(self, validated_data):
|
||||||
groups = validated_data.pop('groups', [])
|
groups = validated_data.pop('groups', [])
|
||||||
rfcs = validated_data.pop('rfc', [])
|
rfcs = validated_data.pop('rfc', [])
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ class EDocumentSerializer(serializers.ModelSerializer):
|
|||||||
numero = str(obj.numero_edocument).strip()
|
numero = str(obj.numero_edocument).strip()
|
||||||
# id_pedimento = str(obj.pedimento_id).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(
|
qs = Document.objects.filter(
|
||||||
pedimento=obj.pedimento,
|
pedimento=obj.pedimento,
|
||||||
archivo__icontains=numero,
|
archivo__icontains=numero,
|
||||||
@@ -240,15 +240,11 @@ class CoveSerializer(serializers.ModelSerializer):
|
|||||||
try:
|
try:
|
||||||
numero = str(obj.numero_cove).strip()
|
numero = str(obj.numero_cove).strip()
|
||||||
|
|
||||||
# Excluir los tipo de documento 20, 24, 23 y 19
|
# Excluir solo request (19, 23); errores (20, 24) se incluyen para detección en frontend
|
||||||
# 20 = error solicitud cove
|
|
||||||
# 24 = error solicitud acuse cove
|
|
||||||
# 23 = request acuse cove
|
|
||||||
# 19 = request cove
|
|
||||||
qs = Document.objects.filter(
|
qs = Document.objects.filter(
|
||||||
pedimento=obj.pedimento,
|
pedimento=obj.pedimento,
|
||||||
archivo__icontains=numero,
|
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
|
# Filtro por organización si aplica
|
||||||
if hasattr(obj, 'organizacion') and obj.organizacion:
|
if hasattr(obj, 'organizacion') and obj.organizacion:
|
||||||
|
|||||||
@@ -91,12 +91,18 @@ def procesar_coves_pedimento(pedimento_id):
|
|||||||
"credencial": credenciales_dict
|
"credencial": credenciales_dict
|
||||||
}
|
}
|
||||||
|
|
||||||
response = requests.post(
|
try:
|
||||||
f"{SERVICE_API_URL_V2}/services/all/coves",
|
response = requests.post(
|
||||||
data=json.dumps(payload),
|
f"{SERVICE_API_URL_V2}/services/all/coves",
|
||||||
headers={"Content-Type": "application/json"}
|
data=json.dumps(payload),
|
||||||
)
|
headers={"Content-Type": "application/json"},
|
||||||
print(f"Servicio de COVEs enviado para pedimento {pedimento.pedimento}")
|
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
|
@shared_task
|
||||||
def procesar_acuse_coves_pedimento(pedimento_id):
|
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
|
id=CredencialesImportador.objects.filter(rfc=pedimento.contribuyente).first().vucem.id
|
||||||
).first()
|
).first()
|
||||||
credenciales_dict = credenciales_to_dict(credenciales)
|
credenciales_dict = credenciales_to_dict(credenciales)
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
"coves": [cove_to_dict(cove) for cove in pedimento.coves.filter(acuse_cove_descargado=False)],
|
"coves": [cove_to_dict(cove) for cove in pedimento.coves.filter(acuse_cove_descargado=False)],
|
||||||
"pedimento": pedimento_dict,
|
"pedimento": pedimento_dict,
|
||||||
"credencial": credenciales_dict
|
"credencial": credenciales_dict
|
||||||
}
|
}
|
||||||
|
|
||||||
response = requests.post(
|
try:
|
||||||
f"{SERVICE_API_URL_V2}/services/all/acuse/cove/",
|
response = requests.post(
|
||||||
data=json.dumps(payload),
|
f"{SERVICE_API_URL_V2}/services/all/acuse/cove/",
|
||||||
headers={"Content-Type": "application/json"}
|
data=json.dumps(payload),
|
||||||
)
|
headers={"Content-Type": "application/json"},
|
||||||
print(f"Servicio de acuses de COVEs enviado para pedimento {pedimento.pedimento}")
|
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
|
@shared_task
|
||||||
def procesar_edocs_pedimento(pedimento_id):
|
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
|
id=CredencialesImportador.objects.filter(rfc=pedimento.contribuyente).first().vucem.id
|
||||||
).first()
|
).first()
|
||||||
credenciales_dict = credenciales_to_dict(credenciales)
|
credenciales_dict = credenciales_to_dict(credenciales)
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
"edocs": [edoc_to_dict(edoc) for edoc in pedimento.documentos.filter(edocument_descargado=False)],
|
"edocs": [edoc_to_dict(edoc) for edoc in pedimento.documentos.filter(edocument_descargado=False)],
|
||||||
"pedimento": pedimento_dict,
|
"pedimento": pedimento_dict,
|
||||||
"credencial": credenciales_dict
|
"credencial": credenciales_dict
|
||||||
}
|
}
|
||||||
|
|
||||||
response = requests.post(
|
try:
|
||||||
f"{SERVICE_API_URL_V2}/services/download/all/edocs/",
|
response = requests.post(
|
||||||
data=json.dumps(payload),
|
f"{SERVICE_API_URL_V2}/services/download/all/edocs/",
|
||||||
headers={"Content-Type": "application/json"}
|
data=json.dumps(payload),
|
||||||
)
|
headers={"Content-Type": "application/json"},
|
||||||
print(f"Servicio de E-documents enviado para pedimento {pedimento.pedimento}")
|
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
|
@shared_task
|
||||||
def procesar_acuses_pedimento(pedimento_id):
|
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
|
id=CredencialesImportador.objects.filter(rfc=pedimento.contribuyente).first().vucem.id
|
||||||
).first()
|
).first()
|
||||||
credenciales_dict = credenciales_to_dict(credenciales)
|
credenciales_dict = credenciales_to_dict(credenciales)
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
"edocs": [edoc_to_dict(edoc) for edoc in pedimento.documentos.filter(acuse_descargado=False)],
|
"edocs": [edoc_to_dict(edoc) for edoc in pedimento.documentos.filter(acuse_descargado=False)],
|
||||||
"pedimento": pedimento_dict,
|
"pedimento": pedimento_dict,
|
||||||
"credencial": credenciales_dict
|
"credencial": credenciales_dict
|
||||||
}
|
}
|
||||||
|
|
||||||
response = requests.post(
|
try:
|
||||||
f"{SERVICE_API_URL_V2}/services/all/acuse/pedimento/",
|
response = requests.post(
|
||||||
data=json.dumps(payload),
|
f"{SERVICE_API_URL_V2}/services/all/acuse/pedimento/",
|
||||||
headers={"Content-Type": "application/json"}
|
data=json.dumps(payload),
|
||||||
)
|
headers={"Content-Type": "application/json"},
|
||||||
print(f"Servicio de acuses enviado para pedimento {pedimento.pedimento}")
|
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
|
@shared_task
|
||||||
def procesar_partidas_pedimento(pedimento_id):
|
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
|
id=CredencialesImportador.objects.filter(rfc=pedimento.contribuyente).first().vucem.id
|
||||||
).first()
|
).first()
|
||||||
credenciales_dict = credenciales_to_dict(credenciales)
|
credenciales_dict = credenciales_to_dict(credenciales)
|
||||||
|
|
||||||
|
partidas_pendientes = list(pedimento.partidas.filter(descargado=False))
|
||||||
payload = {
|
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,
|
"pedimento": pedimento_dict,
|
||||||
"credencial": credenciales_dict
|
"credencial": credenciales_dict
|
||||||
}
|
}
|
||||||
|
|
||||||
response = requests.post(
|
try:
|
||||||
f"{SERVICE_API_URL_V2}/services/all/partidas/",
|
response = requests.post(
|
||||||
data=json.dumps(payload),
|
f"{SERVICE_API_URL_V2}/services/all/partidas/",
|
||||||
headers={"Content-Type": "application/json"}
|
data=json.dumps(payload),
|
||||||
)
|
headers={"Content-Type": "application/json"},
|
||||||
print(f"Servicio de partidas enviado para pedimento {pedimento.pedimento}")
|
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
|
@shared_task
|
||||||
def procesar_remesas_pedimento(pedimento_id):
|
def procesar_remesas_pedimento(pedimento_id):
|
||||||
@@ -205,12 +242,18 @@ def procesar_remesas_pedimento(pedimento_id):
|
|||||||
"credencial": credenciales_dict
|
"credencial": credenciales_dict
|
||||||
}
|
}
|
||||||
|
|
||||||
response = requests.post(
|
try:
|
||||||
f"{SERVICE_API_URL_V2}/services/remesas",
|
response = requests.post(
|
||||||
data=json.dumps(payload),
|
f"{SERVICE_API_URL_V2}/services/remesas",
|
||||||
headers={"Content-Type": "application/json"}
|
data=json.dumps(payload),
|
||||||
)
|
headers={"Content-Type": "application/json"},
|
||||||
print(f"Servicio de remesas enviado para pedimento {pedimento.pedimento}")
|
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
|
@shared_task
|
||||||
def procesar_pedimento_completo_individual(pedimento_id):
|
def procesar_pedimento_completo_individual(pedimento_id):
|
||||||
@@ -225,13 +268,19 @@ def procesar_pedimento_completo_individual(pedimento_id):
|
|||||||
"pedimento": pedimento_dict,
|
"pedimento": pedimento_dict,
|
||||||
"credencial": credenciales_dict
|
"credencial": credenciales_dict
|
||||||
}
|
}
|
||||||
response = requests.post(
|
try:
|
||||||
f"{SERVICE_API_URL_V2}/services/pedimento_completo",
|
response = requests.post(
|
||||||
data=json.dumps(payload),
|
f"{SERVICE_API_URL_V2}/services/pedimento_completo",
|
||||||
headers={"Content-Type": "application/json"}
|
data=json.dumps(payload),
|
||||||
)
|
headers={"Content-Type": "application/json"},
|
||||||
print(f"Servicio enviado para pedimento {pedimento.pedimento}")
|
timeout=60
|
||||||
return response
|
)
|
||||||
|
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
|
@shared_task
|
||||||
def procesar_pedimentos_completos(organizacion_id):
|
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"
|
url = f"{SERVICE_API_URL_V2}/services/pedimento_completo"
|
||||||
dataJson = json.dumps(payload)
|
dataJson = json.dumps(payload)
|
||||||
|
|
||||||
response = requests.post(
|
try:
|
||||||
url,
|
response = requests.post(
|
||||||
data=dataJson,
|
url,
|
||||||
headers={"Content-Type": "application/json"}
|
data=dataJson,
|
||||||
)
|
headers={"Content-Type": "application/json"},
|
||||||
# Aquí puedes continuar con el resto de tu lógica
|
timeout=60
|
||||||
print(f"Servicio enviado para pedimento {pedimento.pedimento}")
|
)
|
||||||
|
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
|
@shared_task
|
||||||
def procesar_remesas(organizacion_id):
|
def procesar_remesas(organizacion_id):
|
||||||
@@ -311,9 +365,11 @@ def procesar_remesas(organizacion_id):
|
|||||||
response = requests.post(
|
response = requests.post(
|
||||||
f"{SERVICE_API_URL_V2}/services/remesas/",
|
f"{SERVICE_API_URL_V2}/services/remesas/",
|
||||||
data=json.dumps(payload),
|
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:
|
except Exception as e:
|
||||||
logger.error(f"Error procesando remesa para pedimento {pedimento.pedimento}: {e}", exc_info=True)
|
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
|
"credencial": credenciales_dict
|
||||||
}
|
}
|
||||||
|
|
||||||
response = requests.post(
|
try:
|
||||||
f"{SERVICE_API_URL_V2}/services/all/coves",
|
response = requests.post(
|
||||||
data=json.dumps(payload),
|
f"{SERVICE_API_URL_V2}/services/all/coves",
|
||||||
headers={"Content-Type": "application/json"}
|
data=json.dumps(payload),
|
||||||
)
|
headers={"Content-Type": "application/json"},
|
||||||
# Aquí puedes continuar con el resto de tu lógica
|
timeout=60
|
||||||
|
)
|
||||||
print(f"Servicio enviado para pedimento {pedimento.pedimento}")
|
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
|
@shared_task
|
||||||
def procesar_acuse_coves(organizacion_id):
|
def procesar_acuse_coves(organizacion_id):
|
||||||
@@ -370,14 +430,18 @@ def procesar_acuse_coves(organizacion_id):
|
|||||||
"credencial": credenciales_dict
|
"credencial": credenciales_dict
|
||||||
}
|
}
|
||||||
|
|
||||||
response = requests.post(
|
try:
|
||||||
f"{SERVICE_API_URL_V2}/services/all/acuse/cove/",
|
response = requests.post(
|
||||||
data=json.dumps(payload),
|
f"{SERVICE_API_URL_V2}/services/all/acuse/cove/",
|
||||||
headers={"Content-Type": "application/json"}
|
data=json.dumps(payload),
|
||||||
)
|
headers={"Content-Type": "application/json"},
|
||||||
# Aquí puedes continuar con el resto de tu lógica
|
timeout=60
|
||||||
|
)
|
||||||
print(f"Servicio enviado para pedimento {pedimento.pedimento}")
|
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
|
@shared_task
|
||||||
def procesar_acuses(organizacion_id):
|
def procesar_acuses(organizacion_id):
|
||||||
@@ -401,14 +465,18 @@ def procesar_acuses(organizacion_id):
|
|||||||
"credencial": credenciales_dict
|
"credencial": credenciales_dict
|
||||||
}
|
}
|
||||||
|
|
||||||
response = requests.post(
|
try:
|
||||||
f"{SERVICE_API_URL_V2}/services/all/acuse/pedimento/",
|
response = requests.post(
|
||||||
data=json.dumps(payload),
|
f"{SERVICE_API_URL_V2}/services/all/acuse/pedimento/",
|
||||||
headers={"Content-Type": "application/json"}
|
data=json.dumps(payload),
|
||||||
)
|
headers={"Content-Type": "application/json"},
|
||||||
# Aquí puedes continuar con el resto de tu lógica
|
timeout=60
|
||||||
|
)
|
||||||
print(f"Servicio enviado para pedimento {pedimento.pedimento}")
|
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
|
@shared_task
|
||||||
def procesar_edocs(organizacion_id):
|
def procesar_edocs(organizacion_id):
|
||||||
@@ -432,14 +500,18 @@ def procesar_edocs(organizacion_id):
|
|||||||
"credencial": credenciales_dict
|
"credencial": credenciales_dict
|
||||||
}
|
}
|
||||||
|
|
||||||
response = requests.post(
|
try:
|
||||||
f"{SERVICE_API_URL_V2}/services/download/all/edocs/",
|
response = requests.post(
|
||||||
data=json.dumps(payload),
|
f"{SERVICE_API_URL_V2}/services/download/all/edocs/",
|
||||||
headers={"Content-Type": "application/json"}
|
data=json.dumps(payload),
|
||||||
)
|
headers={"Content-Type": "application/json"},
|
||||||
# Aquí puedes continuar con el resto de tu lógica
|
timeout=60
|
||||||
|
)
|
||||||
print(f"Servicio enviado para pedimento {pedimento.pedimento}")
|
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
|
@shared_task
|
||||||
def procesar_partidas(organizacion_id):
|
def procesar_partidas(organizacion_id):
|
||||||
@@ -447,29 +519,42 @@ def procesar_partidas(organizacion_id):
|
|||||||
organizacion_id=organizacion_id,
|
organizacion_id=organizacion_id,
|
||||||
partidas__isnull=False
|
partidas__isnull=False
|
||||||
).distinct()
|
).distinct()
|
||||||
|
|
||||||
for pedimento in pedimentos:
|
for pedimento in pedimentos:
|
||||||
if pedimento.partidas.filter(descargado=False).exists(): # Tipo 4: Partidas
|
partidas_pendientes = list(pedimento.partidas.filter(descargado=False))
|
||||||
# Convertir el pedimento a JSON usando el serializer
|
if not partidas_pendientes:
|
||||||
pedimento_dict = pedimento_to_dict(pedimento)
|
continue
|
||||||
credenciales = Vucem.objects.filter(id=CredencialesImportador.objects.filter(rfc=pedimento.contribuyente).first().vucem.id).first()
|
|
||||||
|
|
||||||
credenciales_dict = credenciales_to_dict(credenciales)
|
pedimento_dict = pedimento_to_dict(pedimento)
|
||||||
|
credenciales = Vucem.objects.filter(
|
||||||
payload = {
|
id=CredencialesImportador.objects.filter(rfc=pedimento.contribuyente).first().vucem.id
|
||||||
"partidas": [partida_to_dict(partida) for partida in pedimento.partidas.filter(descargado=False)],
|
).first()
|
||||||
"pedimento": pedimento_dict,
|
credenciales_dict = credenciales_to_dict(credenciales)
|
||||||
"credencial": credenciales_dict
|
|
||||||
}
|
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"partidas": [partida_to_dict(p) for p in partidas_pendientes],
|
||||||
|
"pedimento": pedimento_dict,
|
||||||
|
"credencial": credenciales_dict
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
f"{SERVICE_API_URL_V2}/services/all/partidas/",
|
f"{SERVICE_API_URL_V2}/services/all/partidas/",
|
||||||
data=json.dumps(payload),
|
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
|
response.raise_for_status()
|
||||||
|
result = response.json()
|
||||||
print(f"Servicio enviado para pedimento {pedimento.pedimento}")
|
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
|
@shared_task
|
||||||
def documentos_con_errores(organizacion_id):
|
def documentos_con_errores(organizacion_id):
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ from .views_auditor import (
|
|||||||
auditor_obtener_peticion_edocument_vu,
|
auditor_obtener_peticion_edocument_vu,
|
||||||
auditor_obtener_respuesta_edocument_vu,
|
auditor_obtener_respuesta_edocument_vu,
|
||||||
auditar_pedimento_endpoint,
|
auditar_pedimento_endpoint,
|
||||||
|
procesar_pedimento_completo_endpoint,
|
||||||
)
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
@@ -80,6 +81,7 @@ urlpatterns = [
|
|||||||
path('auditor/auditar-acuse/pedimento/', auditar_acuse_pedimento_endpoint, name='auditar-acuse-pedimento'),
|
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-remesa/pedimento/', auditar_procesamiento_remesa_pedimento_endpoint, name='auditar-remesa-pedimento'),
|
||||||
path('auditor/auditar-pedimento/', auditar_pedimento_endpoint, name='auditar-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/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'),
|
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',
|
'partial_update': 'edocuments.edit',
|
||||||
'destroy': 'edocuments.delete',
|
'destroy': 'edocuments.delete',
|
||||||
'bulk_delete_edocs_vu': 'edocuments.delete',
|
'bulk_delete_edocs_vu': 'edocuments.delete',
|
||||||
|
'reset_acuse': 'edocuments.edit',
|
||||||
}
|
}
|
||||||
codename = perms.get(self.action, 'edocuments.view')
|
codename = perms.get(self.action, 'edocuments.view')
|
||||||
return [IsAuthenticated(), require_permission(codename)()]
|
return [IsAuthenticated(), require_permission(codename)()]
|
||||||
@@ -2531,6 +2532,88 @@ class ViewSetEDocument(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
|
|||||||
def perform_destroy(self, instance):
|
def perform_destroy(self, instance):
|
||||||
instance.delete()
|
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):
|
class ViewSetCove(viewsets.ModelViewSet, OrganizacionFiltradaMixin):
|
||||||
"""
|
"""
|
||||||
ViewSet for Cove model.
|
ViewSet for Cove model.
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ from .tasks.auditoria import (
|
|||||||
auditar_remesas,
|
auditar_remesas,
|
||||||
)
|
)
|
||||||
from .tasks.internal_services import auditar_pedimentos
|
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.customs.models import Pedimento
|
||||||
from api.organization.models import Organizacion
|
from api.organization.models import Organizacion
|
||||||
from api.record.models import Document
|
from api.record.models import Document
|
||||||
@@ -25,6 +25,9 @@ import os
|
|||||||
from api.utils.storage_service import storage_service
|
from api.utils.storage_service import storage_service
|
||||||
import logging
|
import logging
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
_ERROR_DOCUMENT_TYPES = [10, 14, 16, 18, 20, 22, 24, 26]
|
||||||
|
|
||||||
logger = logging.getLogger('api.customs.views_auditor')
|
logger = logging.getLogger('api.customs.views_auditor')
|
||||||
|
|
||||||
def get_document_content(documento):
|
def get_document_content(documento):
|
||||||
@@ -1680,98 +1683,155 @@ def auditor_obtener_respuesta_edocument_vu(request):
|
|||||||
@permission_classes([IsAuthenticated, require_permission('auditoria.process')])
|
@permission_classes([IsAuthenticated, require_permission('auditoria.process')])
|
||||||
def auditar_pedimento_endpoint(request):
|
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')
|
pedimento_id = request.data.get('pedimento_id')
|
||||||
|
|
||||||
if not pedimento_id:
|
if not pedimento_id:
|
||||||
return Response(
|
return Response(
|
||||||
{'error': 'Debe proporcionar pedimento_id'},
|
{'error': 'Debe proporcionar pedimento_id'},
|
||||||
status=status.HTTP_400_BAD_REQUEST
|
status=status.HTTP_400_BAD_REQUEST
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Validar permisos y existencia del pedimento
|
pedimento = Pedimento.objects.select_related(
|
||||||
pedimento = Pedimento.objects.get(id=pedimento_id)
|
'organizacion', 'contribuyente'
|
||||||
|
).get(id=pedimento_id)
|
||||||
user = request.user
|
user = request.user
|
||||||
|
|
||||||
if not user.is_superuser and str(pedimento.organizacion.id) != str(user.organizacion.id):
|
if not user.is_superuser and str(pedimento.organizacion.id) != str(user.organizacion.id):
|
||||||
return Response(
|
return Response(
|
||||||
{'error': 'No tiene permisos para este pedimento'},
|
{'error': 'No tiene permisos para este pedimento'},
|
||||||
status=status.HTTP_403_FORBIDDEN
|
status=status.HTTP_403_FORBIDDEN
|
||||||
)
|
)
|
||||||
|
|
||||||
# Buscar documentos XML del pedimento
|
# PC descargado (type 2)
|
||||||
documentos_xml = Document.objects.filter(
|
pc_descargado = pedimento.documents.filter(
|
||||||
pedimento=pedimento,
|
document_type_id=2,
|
||||||
archivo__endswith='.xml',
|
|
||||||
organizacion=pedimento.organizacion
|
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')
|
||||||
)
|
)
|
||||||
|
errores_detectados = [
|
||||||
if not documentos_xml.exists():
|
{
|
||||||
return Response({
|
'documento_id': str(doc.id),
|
||||||
'pedimento_id': str(pedimento_id),
|
'nombre_archivo': os.path.basename(str(doc.archivo)),
|
||||||
'pedimento': pedimento.pedimento,
|
'tipo_error': doc.document_type.descripcion if doc.document_type else 'Error desconocido',
|
||||||
'pedimento_app': pedimento.pedimento_app,
|
'tipo_id': doc.document_type_id,
|
||||||
'archivos_xml_encontrados': 0,
|
}
|
||||||
'mensaje': 'No se encontraron archivos XML para este pedimento',
|
for doc in docs_error
|
||||||
'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:
|
|
||||||
|
|
||||||
print(f"documento >>>> {documento}")
|
# XML del PC si existe
|
||||||
logger.info(f"documento >>>> {documento}")
|
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:
|
hay_pendientes = not pc_descargado
|
||||||
xml_info = {
|
hay_errores = bool(errores_detectados)
|
||||||
'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)
|
|
||||||
|
|
||||||
# Actualizar el pedimento con la información encontrada si es necesario
|
if hay_errores:
|
||||||
actualizar_info_pedimento(pedimento, info_pedimento)
|
estado = 'CON_ERRORES'
|
||||||
|
elif hay_pendientes:
|
||||||
xmls_analizados.append(xml_info)
|
estado = 'PENDIENTE'
|
||||||
|
else:
|
||||||
except Exception as e:
|
estado = 'COMPLETO'
|
||||||
xmls_analizados.append({
|
|
||||||
'documento_id': str(documento.id),
|
return Response({
|
||||||
'nombre_archivo': os.path.basename(str(documento.archivo)),
|
|
||||||
'error': f'Error procesando archivo: {str(e)}'
|
|
||||||
})
|
|
||||||
|
|
||||||
response_data = {
|
|
||||||
'pedimento_id': str(pedimento_id),
|
'pedimento_id': str(pedimento_id),
|
||||||
'pedimento': pedimento.pedimento,
|
'pedimento': pedimento.pedimento,
|
||||||
'pedimento_app': pedimento.pedimento_app,
|
'pedimento_app': pedimento.pedimento_app,
|
||||||
'archivos_xml_encontrados': len(xmls_analizados),
|
'estado': estado,
|
||||||
'xmls_analizados': xmls_analizados,
|
'hay_pendientes': hay_pendientes,
|
||||||
'informacion_extraida': informacion_extraida,
|
'hay_errores': hay_errores,
|
||||||
'auditoria_completa': True,
|
'pc_descargado': pc_descargado,
|
||||||
'mensaje': f'Auditoría completada para el pedimento {pedimento.pedimento}'
|
'puede_procesar': puede_procesar,
|
||||||
}
|
'razones_no_puede_procesar': razones,
|
||||||
|
'fuente': fuente,
|
||||||
return Response(response_data, status=status.HTTP_200_OK)
|
'datos': datos,
|
||||||
|
'validacion': validacion,
|
||||||
|
'errores_detectados': errores_detectados,
|
||||||
|
'informacion_xml': informacion_xml,
|
||||||
|
}, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
except Pedimento.DoesNotExist:
|
except Pedimento.DoesNotExist:
|
||||||
return Response(
|
return Response(
|
||||||
{'error': 'Pedimento no encontrado'},
|
{'error': 'Pedimento no encontrado'},
|
||||||
@@ -1783,6 +1843,164 @@ def auditar_pedimento_endpoint(request):
|
|||||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
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):
|
def actualizar_info_pedimento(pedimento, info_xml):
|
||||||
"""
|
"""
|
||||||
Actualiza la información del pedimento con los datos extraídos del XML.
|
Actualiza la información del pedimento con los datos extraídos del XML.
|
||||||
|
|||||||
Reference in New Issue
Block a user