feature/implementacion de hub en EFC

This commit is contained in:
2026-06-08 07:19:01 -06:00
parent a9931d2838
commit e1716d65a7
20 changed files with 3749 additions and 649 deletions

View File

@@ -1,128 +1,373 @@
import tempfile
from api.utils.storage_service import storage_service
from celery import shared_task
from api.organization.models import Organizacion
from django.utils import timezone
from api.reports.models import ReportDocument
from api.customs.models import Pedimento, Cove, EDocument, Partida
from django.db.models import Q, Exists, OuterRef
# from django.db.models import Q,
from api.record.models import Document
import csv
import io
import logging
import os
from django.conf import settings
from django.core.files.uploadedfile import SimpleUploadedFile
import tempfile
import traceback
from collections import defaultdict
@shared_task
def generate_report_document(report_id):
import openpyxl
from openpyxl.styles import Alignment, Font, PatternFill
from celery import shared_task
from celery.exceptions import SoftTimeLimitExceeded
from django.core.files.uploadedfile import SimpleUploadedFile
from django.db.models import Q
from django.utils import timezone
from api.customs.models import Cove, EDocument, Partida, Pedimento
from api.organization.models import Organizacion
from api.record.models import Document
from api.reports.models import ReportDocument
from api.utils.storage_service import storage_service
from core.redis_events import publish_task_event
logger = logging.getLogger('api.reports.tasks')
# ── helpers ───────────────────────────────────────────────────────────────────
def _estado(flag: bool) -> str:
return 'RECUPERADO' if flag else 'PENDIENTE'
def _build_pedimento_filters(filters: dict) -> Q:
q = Q()
if filters.get('organizacion_id'):
q &= Q(organizacion_id=filters['organizacion_id'])
if filters.get('fecha_pago__gte'):
q &= Q(fecha_pago__gte=filters['fecha_pago__gte'])
if filters.get('fecha_pago__lte'):
q &= Q(fecha_pago__lte=filters['fecha_pago__lte'])
if filters.get('patente'):
q &= Q(patente=filters['patente'])
if filters.get('aduana'):
q &= Q(aduana=filters['aduana'])
if filters.get('pedimento'):
q &= Q(pedimento=filters['pedimento'])
if filters.get('pedimento_app'):
q &= Q(pedimento_app=filters['pedimento_app'])
if filters.get('regimen'):
q &= Q(regimen=filters['regimen'])
if filters.get('tipo_operacion'):
q &= Q(tipo_operacion_id=filters['tipo_operacion'])
rfc_val = filters.get('contribuyente__rfc')
if rfc_val:
if rfc_val == 'SIN_RFC':
q &= Q(contribuyente__isnull=True)
else:
q &= Q(contribuyente__rfc=rfc_val)
return q
def _apply_user_rfc_filter(q: Q, user, requested_rfc: str | None) -> Q:
"""Restringe el queryset a los importadores visibles del usuario."""
# SIN_RFC ya fue aplicado en _build_pedimento_filters como contribuyente__isnull=True
if requested_rfc == 'SIN_RFC':
return q
user_rfcs = user.rfc.all()
if not user_rfcs.exists():
if requested_rfc:
q &= Q(contribuyente__rfc=requested_rfc)
return q
if requested_rfc:
if user_rfcs.filter(rfc=requested_rfc).exists():
q &= Q(contribuyente__rfc=requested_rfc)
else:
q &= Q(contribuyente__in=user_rfcs)
else:
q &= Q(contribuyente__in=user_rfcs)
return q
# ── tarea principal ───────────────────────────────────────────────────────────
@shared_task(bind=True, queue='reports', soft_time_limit=600, time_limit=660)
def generate_report_document(self, report_id):
task_id = self.request.id
report = None
def _fail(msg, exc=None):
"""Marca el reporte como error, notifica al frontend y loguea. Sin re-raise."""
tb = traceback.format_exc() if exc else ''
full_msg = f"{msg}\n\n{tb}".strip() if tb else msg
logger.error('[reporte_cumplimiento] report=%s FALLO: %s', report_id, full_msg)
if report:
report.status = 'error'
report.error_message = full_msg
report.finished_at = timezone.now()
report.save(update_fields=['status', 'error_message', 'finished_at'])
publish_task_event(task_id, 'failed', msg, progress=0)
# ── 1. Obtener reporte ────────────────────────────────────────────────────
try:
report = ReportDocument.objects.get(id=report_id)
report.status = 'processing'
report.save(update_fields=['status'])
filters = report.filters or {}
pedimentos_filters = Q()
if filters.get('organizacion_id'):
pedimentos_filters &= Q(organizacion_id=filters['organizacion_id'])
if filters.get('fecha_pago__gte'):
pedimentos_filters &= Q(fecha_pago__gte=filters['fecha_pago__gte'])
if filters.get('fecha_pago__lte'):
pedimentos_filters &= Q(fecha_pago__lte=filters['fecha_pago__lte'])
if filters.get('contribuyente__rfc'):
pedimentos_filters &= Q(contribuyente__rfc=filters['contribuyente__rfc'])
if filters.get('patente'):
pedimentos_filters &= Q(patente=filters['patente'])
if filters.get('aduana'):
pedimentos_filters &= Q(aduana=filters['aduana'])
if filters.get('pedimento'):
pedimentos_filters &= Q(pedimento=filters['pedimento'])
if filters.get('pedimento_app'):
pedimentos_filters &= Q(pedimento_app=filters['pedimento_app'])
if filters.get('regimen'):
pedimentos_filters &= Q(regimen=filters['regimen'])
if filters.get('tipo_operacion'):
pedimentos_filters &= Q(tipo_operacion_id=filters['tipo_operacion'])
# Consulta asíncrona de los modelos
pedimentos = Pedimento.objects.filter(pedimentos_filters)
filename = filters.get('filename')
if filename:
filename = f"{filename}.csv" if not filename.endswith('.csv') else filename
else:
filename = f"report_{report.id}_{timezone.now().strftime('%Y%m%d%H%M%S')}.csv"
except ReportDocument.DoesNotExist:
logger.error('[reporte_cumplimiento] ReportDocument %s no existe', report_id)
publish_task_event(task_id, 'failed', f'Reporte {report_id} no encontrado', progress=0)
return
with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.csv', encoding='utf-8', newline='') as f:
tmp_path = f.name
# Escribir CSV en archivo temporal
with open(tmp_path, 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
headers = [
'aduana', 'patente', 'regimen', 'pedimento', 'pedimento_app', 'clave_pedimento',
'tipo_operacion_id', 'contribuyente_id', 'tipo_documento', 'numero_documento', 'estado', 'acuse_estado'
]
writer.writerow(headers)
for ped in pedimentos:
for cove in Cove.objects.filter(pedimento=ped):
writer.writerow([
ped.aduana, ped.patente, ped.regimen, ped.pedimento, ped.pedimento_app,
ped.clave_pedimento, ped.tipo_operacion_id, ped.contribuyente_id,
'COVE', cove.numero_cove, cove.cove_descargado, cove.acuse_cove_descargado
])
for edoc in EDocument.objects.filter(pedimento=ped):
writer.writerow([
ped.aduana, ped.patente, ped.regimen, ped.pedimento, ped.pedimento_app,
ped.clave_pedimento, ped.tipo_operacion_id, ped.contribuyente_id,
'EDOC', edoc.numero_edocument, edoc.edocument_descargado, edoc.acuse_descargado
])
for partida in Partida.objects.filter(pedimento=ped):
writer.writerow([
ped.aduana, ped.patente, ped.regimen, ped.pedimento, ped.pedimento_app,
ped.clave_pedimento, ped.tipo_operacion_id, ped.contribuyente_id,
'PARTIDA', partida.numero_partida, partida.descargado, ''
])
# ============ NUEVO: Guardar en MinIO ============
# Leer archivo temporal
with open(tmp_path, 'rb') as f:
file_content = f.read()
# Crear UploadedFile
uploaded_file = SimpleUploadedFile(
name=filename,
content=file_content,
content_type='text/csv'
logger.info('[reporte_cumplimiento] Iniciando report=%s user=%s', report_id, report.user_id)
report.status = 'processing'
report.save(update_fields=['status'])
publish_task_event(task_id, 'processing', 'Iniciando generación de reporte...', progress=5)
try:
filters = report.filters or {}
org_id = filters.get('organizacion_id')
# ── 2. Filtros y organización ─────────────────────────────────────────
q = _build_pedimento_filters(filters)
q = _apply_user_rfc_filter(q, report.user, filters.get('contribuyente__rfc'))
nombre_org = ''
if org_id:
try:
nombre_org = Organizacion.objects.get(id=org_id).nombre
except Organizacion.DoesNotExist:
pass
logger.info('[reporte_cumplimiento] report=%s org=%s filtros=%s', report_id, nombre_org, filters)
publish_task_event(task_id, 'processing', f'Consultando RFCs de {nombre_org}...', progress=10)
# ── 3. Listar RFCs (consulta liviana) ────────────────────────────────
rfcs_list = list(
Pedimento.objects.filter(q)
.exclude(contribuyente__isnull=True)
.values_list('contribuyente__rfc', flat=True)
.distinct()
.order_by('contribuyente__rfc')
)
# Guardar en storage
if Pedimento.objects.filter(q, contribuyente__isnull=True).exists():
rfcs_list.append('SIN_RFC')
total_rfcs = len(rfcs_list)
total_pedimentos = Pedimento.objects.filter(q).count()
logger.info('[reporte_cumplimiento] report=%s total_rfcs=%d total_pedimentos=%d',
report_id, total_rfcs, total_pedimentos)
if total_rfcs == 0:
logger.warning('[reporte_cumplimiento] report=%s sin pedimentos para los filtros dados', report_id)
publish_task_event(
task_id, 'processing',
f'{total_rfcs} RFC(s) — {total_pedimentos} pedimentos', progress=15,
)
# ── 4. Crear workbook ─────────────────────────────────────────────────
wb = openpyxl.Workbook()
ws = wb.active
ws.title = 'Reporte Cumplimiento'
title_fill = PatternFill(start_color='1F4E79', end_color='1F4E79', fill_type='solid')
title_font = Font(color='FFFFFF', bold=True, size=12)
sub_fill = PatternFill(start_color='2E75B6', end_color='2E75B6', fill_type='solid')
sub_font = Font(color='FFFFFF', bold=True, size=10)
col_h_fill = PatternFill(start_color='D6E4F0', end_color='D6E4F0', fill_type='solid')
col_h_font = Font(bold=True, size=10)
footer_fill = PatternFill(start_color='E2EFDA', end_color='E2EFDA', fill_type='solid')
center = Alignment(horizontal='center', vertical='center', wrap_text=True)
top_left = Alignment(horizontal='left', vertical='top', wrap_text=True)
COL_HEADERS = [
'Año', 'Aduana', 'Patente', 'Pedimento',
'Nomenclatura Completo Pedimento', 'Clav', 'Tipo Operación',
'Expediente Sí', 'Documento', 'Estatus',
]
TOTAL_COLS = len(COL_HEADERS)
current_row = 1
safe_total = max(total_rfcs, 1)
# ── 5. Procesar RFC por RFC ───────────────────────────────────────────
for rfc_idx, rfc in enumerate(rfcs_list):
pct = 20 + int((rfc_idx / safe_total) * 65)
publish_task_event(
task_id, 'processing',
f'RFC {rfc_idx + 1}/{total_rfcs}: {rfc}', progress=pct,
)
rfc_q = (
q & Q(contribuyente__isnull=True) if rfc == 'SIN_RFC'
else q & Q(contribuyente__rfc=rfc)
)
peds = list(
Pedimento.objects.filter(rfc_q)
.select_related('contribuyente', 'tipo_operacion')
.order_by('fecha_pago')
)
if not peds:
logger.warning('[reporte_cumplimiento] report=%s rfc=%s sin pedimentos, omitido', report_id, rfc)
continue
ped_ids = [p.id for p in peds]
razon_social = nombre_org or 'Desconocido'
logger.info('[reporte_cumplimiento] report=%s rfc=%s pedimentos=%d',
report_id, rfc, len(peds))
# documentos de este RFC solamente
coves_map: dict = defaultdict(list)
for c in Cove.objects.filter(pedimento_id__in=ped_ids):
coves_map[c.pedimento_id].append(c)
edocs_map: dict = defaultdict(list)
for e in EDocument.objects.filter(pedimento_id__in=ped_ids):
edocs_map[e.pedimento_id].append(e)
partidas_map: dict = defaultdict(list)
for p in Partida.objects.filter(pedimento_id__in=ped_ids).order_by('numero_partida'):
partidas_map[p.pedimento_id].append(p)
remesa_ped_ids: set = set(
Document.objects.filter(pedimento_id__in=ped_ids, document_type_id=15)
.values_list('pedimento_id', flat=True)
)
total_coves = sum(len(v) for v in coves_map.values())
total_edocs = sum(len(v) for v in edocs_map.values())
total_partidas = sum(len(v) for v in partidas_map.values())
est_rows = len(peds) + total_partidas + total_coves * 2 + total_edocs * 2 + len(remesa_ped_ids)
logger.info('[reporte_cumplimiento] report=%s rfc=%s docs coves=%d edocs=%d partidas=%d remesas=%d filas_estimadas=%d',
report_id, rfc, total_coves, total_edocs, total_partidas, len(remesa_ped_ids), est_rows)
# encabezado sección
ws.merge_cells(start_row=current_row, start_column=1, end_row=current_row, end_column=TOTAL_COLS)
cell = ws.cell(row=current_row, column=1, value='Reporte Integración de Expedientes.')
cell.fill, cell.font, cell.alignment = title_fill, title_font, center
current_row += 1
ws.merge_cells(start_row=current_row, start_column=1, end_row=current_row, end_column=TOTAL_COLS)
cell = ws.cell(row=current_row, column=1, value=f'Razón Social Importador: {razon_social}')
cell.fill, cell.font = sub_fill, sub_font
current_row += 1
ws.merge_cells(start_row=current_row, start_column=1, end_row=current_row, end_column=TOTAL_COLS)
cell = ws.cell(row=current_row, column=1, value=f'RFC: {rfc}')
cell.fill, cell.font = sub_fill, sub_font
current_row += 1
for col_i, header in enumerate(COL_HEADERS, 1):
cell = ws.cell(row=current_row, column=col_i, value=header)
cell.fill, cell.font, cell.alignment = col_h_fill, col_h_font, center
current_row += 1
total_exp = len(peds)
exp_con_docs = exp_completos = 0
for ped in peds:
doc_rows = [('PEDIMENTO COMPLETO', _estado(ped.existe_expediente))]
for partida in partidas_map[ped.id]:
doc_rows.append((f'PARTIDA{partida.numero_partida}', _estado(partida.descargado)))
if ped.remesas:
doc_rows.append(('REMESA', _estado(ped.id in remesa_ped_ids)))
for cove in coves_map[ped.id]:
doc_rows.append((f'COVE{cove.numero_cove}', _estado(cove.cove_descargado)))
doc_rows.append((f'ACUSE COVE{cove.numero_cove}', _estado(cove.acuse_cove_descargado)))
for edoc in edocs_map[ped.id]:
doc_rows.append((f'EDOCUMENTO{edoc.numero_edocument}', _estado(edoc.edocument_descargado)))
doc_rows.append((f'ACUSE EDOCUMENTO{edoc.numero_edocument}', _estado(edoc.acuse_descargado)))
if len(doc_rows) > 1:
exp_con_docs += 1
if all(e == 'RECUPERADO' for _, e in doc_rows):
exp_completos += 1
n_rows = len(doc_rows)
start_row = current_row
anio = ped.fecha_pago.year % 100 if ped.fecha_pago else ''
base_vals = [
anio, ped.aduana or '', ped.patente or '', ped.pedimento or '',
ped.pedimento_app or '', ped.clave_pedimento or '',
ped.tipo_operacion.tipo if ped.tipo_operacion else '',
'SI' if ped.existe_expediente else 'NO',
]
# Sin merge_cells — para datasets grandes merge es O(n^2) y cuelga el proceso.
# Los datos base solo se escriben en la primera fila; el resto queda vacío,
# visualmente equivalente al merge pero sin el costo de memoria/CPU.
for offset, (doc_nombre, doc_est) in enumerate(doc_rows):
r = start_row + offset
if offset == 0:
for col, val in enumerate(base_vals, 1):
ws.cell(row=r, column=col, value=val)
ws.cell(row=r, column=9, value=doc_nombre)
ws.cell(row=r, column=10, value=doc_est)
current_row += n_rows
ws.merge_cells(start_row=current_row, start_column=1, end_row=current_row, end_column=TOTAL_COLS)
cell = ws.cell(
row=current_row, column=1,
value=(f'Total de Expedientes= {total_exp} '
f'Total De Expedientes Con Documentos= {exp_con_docs} '
f'Total De Expedientes Completos= {exp_completos}'),
)
cell.fill = footer_fill
cell.font = Font(bold=True)
current_row += 2
del peds, ped_ids, coves_map, edocs_map, partidas_map, remesa_ped_ids
for i, w in enumerate([6, 8, 8, 12, 32, 8, 16, 12, 32, 14], 1):
ws.column_dimensions[openpyxl.utils.get_column_letter(i)].width = w
# ── 6. Serializar y subir ─────────────────────────────────────────────
logger.info('[reporte_cumplimiento] report=%s serializando Excel...', report_id)
publish_task_event(task_id, 'processing', 'Serializando Excel...', progress=88)
filename = f"reporte_cumplimiento_{report.id}_{timezone.now().strftime('%Y%m%d%H%M%S')}.xlsx"
buf = io.BytesIO()
wb.save(buf)
excel_bytes = buf.getvalue()
logger.info('[reporte_cumplimiento] report=%s Excel size=%.1fKB', report_id, len(excel_bytes) / 1024)
publish_task_event(task_id, 'processing', 'Subiendo a almacenamiento...', progress=93)
ruta = storage_service.save_report(
file=uploaded_file,
organizacion_id=filters.get('organizacion_id'),
file=SimpleUploadedFile(
name=filename,
content=excel_bytes,
content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
),
organizacion_id=org_id,
metadata={
'report_id': str(report.id),
'report_type': 'cumplimiento',
'user_id': str(report.user.id) if report.user else None
}
'user_id': str(report.user.id) if report.user else None,
},
)
if ruta:
report.file = ruta
logger.info('[reporte_cumplimiento] report=%s guardado en storage=%s', report_id, ruta)
report.file = ruta
report.status = 'ready'
else:
report.status = 'error'
report.error_message = 'Error al guardar el archivo en storage'
# Limpiar temporal
os.unlink(tmp_path)
_fail('Error al guardar el archivo en almacenamiento (storage retornó None)')
return
report.finished_at = timezone.now()
report.save(update_fields=['status', 'file', 'finished_at', 'error_message'])
except Exception as e:
report.status = 'error'
report.error_message = str(e)
report.finished_at = timezone.now()
report.save(update_fields=['status', 'error_message', 'finished_at'])
resultado = {
'report_id': str(report.id),
'total_rfcs': total_rfcs,
'total_pedimentos': total_pedimentos,
}
publish_task_event(task_id, 'completed', 'Reporte generado exitosamente.', progress=100, resultado=resultado)
logger.info('[reporte_cumplimiento] report=%s COMPLETADO rfcs=%d pedimentos=%d',
report_id, total_rfcs, total_pedimentos)
return resultado
except SoftTimeLimitExceeded:
_fail('El reporte tardó más de 10 minutos y fue cancelado. Intenta con un rango de fechas más acotado.')
except Exception as exc:
_fail(str(exc), exc=exc)
# ── reporte de control de pedimentos (sin cambios) ────────────────────────────
@shared_task
def generate_report_control_pedimento(report_id):
@@ -133,8 +378,6 @@ def generate_report_control_pedimento(report_id):
report.save(update_fields=['status'])
filters = report.filters or {}
# Construir filtros
pedimentos_filters = {}
if filters.get('organizacion_id'):
pedimentos_filters['organizacion_id'] = filters['organizacion_id']
@@ -145,15 +388,12 @@ def generate_report_control_pedimento(report_id):
if filters.get('pedimento_app'):
pedimentos_filters['pedimento_app'] = filters['pedimento_app']
# pedimentos por organizacion
pedimentos_qs = Pedimento.objects.filter(**pedimentos_filters)
pedimentos_total = pedimentos_qs.count()
pedimento_ids = list(pedimentos_qs.values_list('id', flat=True))
rfcs_raw = list(pedimentos_qs.values_list('agente_aduanal', flat=True))
# inicializar totales
pedimentos_completos = 0
total_documentos = 0
documentos_sin_descargar = 0
@@ -161,17 +401,15 @@ def generate_report_control_pedimento(report_id):
nombre_organizacion = ''
if filters.get('organizacion_id'):
try:
# Asumo que tienes un modelo Organizacion - ajusta según tu modelo real
organizacion = Organizacion.objects.get(id=filters['organizacion_id'])
nombre_organizacion = organizacion.nombre # ajusta el campo según tu modelo
nombre_organizacion = organizacion.nombre
except Organizacion.DoesNotExist:
nombre_organizacion = f"ID: {filters['organizacion_id']}"
except Exception as e:
nombre_organizacion = f"Error: {str(e)}"
# lista de rfc
rfc_list = ', '.join(sorted(set([rfc for rfc in rfcs_raw if rfc])))
fecha_inicio = ''
fecha_fin = ''
@@ -179,109 +417,78 @@ def generate_report_control_pedimento(report_id):
primer_pedimento = pedimentos_qs.order_by('fecha_pago').first()
if primer_pedimento and primer_pedimento.fecha_pago:
fecha_inicio = primer_pedimento.fecha_pago.strftime('%Y-%m-%d')
ultimo_pedimento = pedimentos_qs.order_by('-fecha_pago').first()
if ultimo_pedimento and ultimo_pedimento.fecha_pago:
fecha_fin = ultimo_pedimento.fecha_pago.strftime('%Y-%m-%d')
# Para cada pedimento, verificar si está completo
for pedimento in pedimentos_qs:
# Contar documentos de este pedimento
docs_pedimento = 0
docs_pendientes_pedimento = 0
# COVES
coves_count = Cove.objects.filter(pedimento_id=pedimento.id).count()
coves_pendientes = Cove.objects.filter(pedimento_id=pedimento.id, cove_descargado=False).count()
docs_pedimento += coves_count
docs_pendientes_pedimento += coves_pendientes
# PARTIDAS
partidas_count = Partida.objects.filter(pedimento_id=pedimento.id).count()
partidas_pendientes = Partida.objects.filter(pedimento_id=pedimento.id, descargado=False).count()
docs_pedimento += partidas_count
docs_pendientes_pedimento += partidas_pendientes
# EDOCUMENTS
edocs_count = EDocument.objects.filter(pedimento_id=pedimento.id).count()
edocs_pendientes = EDocument.objects.filter(pedimento_id=pedimento.id, edocument_descargado=False).count()
docs_pedimento += edocs_count
docs_pendientes_pedimento += edocs_pendientes
# Acumular totales
total_documentos += docs_pedimento
documentos_sin_descargar += docs_pendientes_pedimento
# Si no tiene documentos pendientes, está completo
if docs_pendientes_pedimento == 0 and docs_pedimento > 0:
pedimentos_completos += 1
# 3. PORCENTAJE
porcentaje_faltantes = (documentos_sin_descargar / total_documentos * 100) if total_documentos > 0 else 0
# 4. GENERAR CSV CON DETALLES
filename = f"report_{report.id}_{timezone.now().strftime('%Y%m%d%H%M%S')}.csv"
with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.csv', encoding='utf-8', newline='') as tmp:
tmp_path = tmp.name
todas_las_filas = []
# Recopilar datos detallados - UNA FILA POR CADA DOCUMENTO
for pedimento in pedimentos_qs:
# DATOS BASE DEL PEDIMENTO (se repiten en cada fila)
datos_base_pedimento = [
pedimento.aduana or '',
pedimento.patente or '',
pedimento.regimen or '',
pedimento.pedimento or '', # No. Pedimento (7 dígitos)
pedimento.pedimento_app or '', # No. Pedimento App completo
pedimento.pedimento or '',
pedimento.pedimento_app or '',
pedimento.clave_pedimento or '',
pedimento.tipo_operacion.tipo if pedimento.tipo_operacion else '',
str(pedimento.contribuyente_id) if pedimento.contribuyente_id else ''
]
# COVES - Una fila por cada COVE
coves = Cove.objects.filter(pedimento_id=pedimento.id)
for cove in coves:
estado = 'VERDADERO' if cove.cove_descargado else 'FALSO'
fila = datos_base_pedimento + [
# str(cove.id), # Identificador de documento
cove.numero_cove,
'COVE', # Tipo de documento
estado
]
fila = datos_base_pedimento + [cove.numero_cove, 'COVE', estado]
todas_las_filas.append(fila)
# PARTIDAS - Una fila por cada Partida
partidas = Partida.objects.filter(pedimento_id=pedimento.id)
for partida in partidas:
estado = 'VERDADERO' if partida.descargado else 'FALSO'
fila = datos_base_pedimento + [
# str(partida.id),
partida.numero_partida,
'PARTIDA', # Tipo de documento
estado
]
fila = datos_base_pedimento + [partida.numero_partida, 'PARTIDA', estado]
todas_las_filas.append(fila)
# EDOCUMENTS - Una fila por cada EDocument
edocuments = EDocument.objects.filter(pedimento_id=pedimento.id)
for edoc in edocuments:
estado = 'VERDADERO' if edoc.edocument_descargado else 'FALSO'
fila = datos_base_pedimento + [
# str(edoc.id),
edoc.numero_edocument,
'EDOCUMENT', # Tipo de documento
estado
]
fila = datos_base_pedimento + [edoc.numero_edocument, 'EDOCUMENT', estado]
todas_las_filas.append(fila)
# 5. ESCRIBIR ARCHIVO CSV
import csv
with open(tmp_path, 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
# SECCIÓN DE TOTALES
writer.writerow(['RESUMEN DEL REPORTE - CONTROL DE PEDIMENTOS'])
writer.writerow(['ORGANIZACION:', nombre_organizacion])
writer.writerow([])
@@ -294,20 +501,15 @@ def generate_report_control_pedimento(report_id):
writer.writerow(['LISTA RFC:', rfc_list])
writer.writerow([])
writer.writerow([])
# ENCABEZADOS DE DATOS (según requerimiento)
headers = [
'ADUANA', 'PATENTE', 'REGIMEN', 'NO. PEDIMENTO', 'PEDIMENTO_APP',
'CLAVE_PEDIMENTO', 'TIPO_OPERACION', 'CONTRIBUYENTE_ID',
'ADUANA', 'PATENTE', 'REGIMEN', 'NO. PEDIMENTO', 'PEDIMENTO_APP',
'CLAVE_PEDIMENTO', 'TIPO_OPERACION', 'CONTRIBUYENTE_ID',
'IDENTIFICADOR_DOCUMENTO', 'TIPO_DOCUMENTO', 'ESTADO'
]
writer.writerow(headers)
# DATOS DETALLADOS
for fila in todas_las_filas:
writer.writerow(fila)
with open(tmp_path, 'rb') as f:
file_content = f.read()
@@ -344,4 +546,4 @@ def generate_report_control_pedimento(report_id):
report.status = 'error'
report.error_message = str(e)
report.finished_at = timezone.now()
report.save(update_fields=['status', 'error_message', 'finished_at'])
report.save(update_fields=['status', 'error_message', 'finished_at'])