from django.shortcuts import render, get_object_or_404, redirect from django.http import HttpResponse, JsonResponse from django.contrib import messages from django.template.loader import render_to_string from django.views.generic import ListView, CreateView, UpdateView, DeleteView from django.urls import reverse_lazy from xhtml2pdf import pisa from django.conf import settings from .models import InventarioAdministrativo, InventarioTactico, Directores, Subjefes, Unidades, AsignacionAdministrativa, AsignadaUnidadAdministrativa, AsignacionTactica, AsignadaUnidadTactica from .forms import InventarioAdministrativoForm, InventarioTacticoForm, DirectoresForm, SubjefesForm, UnidadForm, AsignacionAdministrativaForm, AsignacionTacticaForm from django.utils.decorators import method_decorator from django.contrib.auth.decorators import login_required from django.contrib.auth.views import LoginView import reportlab.graphics.barcode.code128 import reportlab.graphics.barcode.code39 class CustomLoginView(LoginView): template_name = 'registration/login.html' redirect_authenticated_user = True def form_valid(self, form): response = super().form_valid(form) # Reiniciar el timer al hacer login if hasattr(self.request, 'session'): self.request.session.modified = True return response def form_invalid(self, form): messages.error(self.request, 'Usuario o contraseña incorrectos') return super().form_invalid(form) # Vista principal @login_required def principal(request): vehiculos_administrativos_count = InventarioAdministrativo.objects.count() vehiculos_tacticos_count = InventarioTactico.objects.count() unidades_count = Unidades.objects.count() request.session.modified = True request.session.set_expiry(settings.SESSION_COOKIE_AGE) context = { 'vehiculos_administrativos_count': vehiculos_administrativos_count, 'vehiculos_tacticos_count': vehiculos_tacticos_count, 'unidades_count': unidades_count, 'session_timeout': settings.SESSION_COOKIE_AGE, 'warning_time': settings.WARNING_TIME, } return render(request, 'principal/principal.html', context) @login_required def obtener_datos(request): vehiculos_administrativos_count = InventarioAdministrativo.objects.count() vehiculos_tacticos_count = InventarioTactico.objects.count() unidades_count = Unidades.objects.count() data = { 'vehiculos_administrativos': vehiculos_administrativos_count, 'vehiculos_tacticos': vehiculos_tacticos_count, 'unidades': unidades_count, } return JsonResponse(data) # Vistas genéricas para Directores @method_decorator(login_required, name='dispatch') class DirectoresListView(ListView): model = Directores template_name = 'directores/directores.html' context_object_name = 'directores' @method_decorator(login_required, name='dispatch') class DirectoresCreateView(CreateView): model = Directores form_class = DirectoresForm template_name = 'directores/crear.html' success_url = reverse_lazy('listar_directores') def form_valid(self, form): response = super().form_valid(form) messages.success(self.request, 'Director registrado con éxito.') if self.request.headers.get('X-Requested-With') == 'XMLHttpRequest': return HttpResponse('OK') return response @method_decorator(login_required, name='dispatch') class DirectoresUpdateView(UpdateView): model = Directores form_class = DirectoresForm template_name = 'directores/editar.html' success_url = reverse_lazy('listar_directores') def form_valid(self, form): response = super().form_valid(form) messages.success(self.request, 'Director actualizado con éxito.') if self.request.headers.get('X-Requested-With') == 'XMLHttpRequest': return HttpResponse('OK') return response @method_decorator(login_required, name='dispatch') class DirectoresDeleteView(DeleteView): model = Directores template_name = 'directores/eliminar_director.html' success_url = reverse_lazy('listar_directores') def delete(self, request, *args, **kwargs): self.object = self.get_object() self.object.delete() messages.success(request, 'Director eliminado con éxito.') if request.headers.get('X-Requested-With') == 'XMLHttpRequest': return HttpResponse('OK') return redirect(self.success_url) # Vistas genéricas para Subjefes @method_decorator(login_required, name='dispatch') class SubjefesListView(ListView): model = Subjefes template_name = 'jefes/jefes.html' context_object_name = 'subjefes' @method_decorator(login_required, name='dispatch') class SubjefesCreateView(CreateView): model = Subjefes form_class = SubjefesForm template_name = 'jefes/crear.html' success_url = reverse_lazy('listar_subjefes') def form_valid(self, form): response = super().form_valid(form) messages.success(self.request, 'Grupo registrado con éxito.') if self.request.headers.get('X-Requested-With') == 'XMLHttpRequest': return HttpResponse('OK') return response @method_decorator(login_required, name='dispatch') class SubjefesUpdateView(UpdateView): model = Subjefes form_class = SubjefesForm template_name = 'jefes/editar.html' success_url = reverse_lazy('listar_subjefes') def form_valid(self, form): response = super().form_valid(form) messages.success(self.request, 'Grupo actualizado con éxito.') if self.request.headers.get('X-Requested-With') == 'XMLHttpRequest': return HttpResponse('OK') return response @method_decorator(login_required, name='dispatch') class SubjefesDeleteView(DeleteView): model = Subjefes template_name = 'jefes/eliminar_subjefe.html' success_url = reverse_lazy('listar_subjefes') def delete(self, request, *args, **kwargs): self.object = self.get_object() self.object.delete() messages.success(request, 'Grupo eliminado con éxito.') if request.headers.get('X-Requested-With') == 'XMLHttpRequest': return HttpResponse('OK') return redirect(self.success_url) # Vistas genéricas para Inventario Administrativo @method_decorator(login_required, name='dispatch') class InventarioAdministrativoListView(ListView): model = InventarioAdministrativo template_name = 'inventario_administrativo/listar.html' context_object_name = 'inventarios' @method_decorator(login_required, name='dispatch') class InventarioAdministrativoCreateView(CreateView): model = InventarioAdministrativo form_class = InventarioAdministrativoForm template_name = 'inventario_administrativo/crear.html' success_url = reverse_lazy('listar_inventario_administrativo') def form_valid(self, form): response = super().form_valid(form) messages.success(self.request, 'Vehículo administrativo registrado con éxito.') if self.request.headers.get('X-Requested-With') == 'XMLHttpRequest': return HttpResponse('OK') return response @method_decorator(login_required, name='dispatch') class InventarioAdministrativoUpdateView(UpdateView): model = InventarioAdministrativo form_class = InventarioAdministrativoForm template_name = 'inventario_administrativo/editar.html' success_url = reverse_lazy('listar_inventario_administrativo') def form_valid(self, form): response = super().form_valid(form) messages.success(self.request, 'Vehículo administrativo actualizado con éxito.') if self.request.headers.get('X-Requested-With') == 'XMLHttpRequest': return HttpResponse('OK') return response @method_decorator(login_required, name='dispatch') class InventarioAdministrativoDeleteView(DeleteView): model = InventarioAdministrativo template_name = 'inventario_administrativo/eliminacion.html' success_url = reverse_lazy('listar_inventario_administrativo') def delete(self, request, *args, **kwargs): self.object = self.get_object() self.object.delete() messages.success(request, 'Vehículo administrativo eliminado con éxito.') if request.headers.get('X-Requested-With') == 'XMLHttpRequest': return HttpResponse('OK') return redirect(self.success_url) # Vistas genéricas para Inventario Táctico @method_decorator(login_required, name='dispatch') class InventarioTacticoListView(ListView): model = InventarioTactico template_name = 'inventario_tactico/listar.html' context_object_name = 'inventarios' @method_decorator(login_required, name='dispatch') class InventarioTacticoCreateView(CreateView): model = InventarioTactico form_class = InventarioTacticoForm template_name = 'inventario_tactico/crear.html' success_url = reverse_lazy('listar_inventario_tactico') def form_valid(self, form): response = super().form_valid(form) messages.success(self.request, 'Vehículo táctico registrado con éxito.') if self.request.headers.get('X-Requested-With') == 'XMLHttpRequest': return HttpResponse('OK') return response @method_decorator(login_required, name='dispatch') class InventarioTacticoUpdateView(UpdateView): model = InventarioTactico form_class = InventarioTacticoForm template_name = 'inventario_tactico/editar.html' success_url = reverse_lazy('listar_inventario_tactico') def form_valid(self, form): response = super().form_valid(form) messages.success(self.request, 'Vehículo táctico actualizado con éxito.') if self.request.headers.get('X-Requested-With') == 'XMLHttpRequest': return HttpResponse('OK') return response @method_decorator(login_required, name='dispatch') class InventarioTacticoDeleteView(DeleteView): model = InventarioTactico template_name = 'inventario_tactico/eliminar.html' success_url = reverse_lazy('listar_inventario_tactico') def delete(self, request, *args, **kwargs): self.object = self.get_object() self.object.delete() messages.success(request, 'Vehículo táctico eliminado con éxito.') if request.headers.get('X-Requested-With') == 'XMLHttpRequest': return HttpResponse('OK') return redirect(self.success_url) # Vistas genéricas para Unidades @method_decorator(login_required, name='dispatch') class UnidadListView(ListView): model = Unidades template_name = 'unidad/listar.html' context_object_name = 'unidades' @method_decorator(login_required, name='dispatch') class UnidadCreateView(CreateView): model = Unidades form_class = UnidadForm template_name = 'unidad/crear.html' success_url = reverse_lazy('unidad_list') def form_valid(self, form): response = super().form_valid(form) messages.success(self.request, 'Unidad registrada con éxito.') if self.request.headers.get('X-Requested-With') == 'XMLHttpRequest': return HttpResponse('OK') return response @method_decorator(login_required, name='dispatch') class UnidadUpdateView(UpdateView): model = Unidades form_class = UnidadForm template_name = 'unidad/editar.html' success_url = reverse_lazy('unidad_list') def form_valid(self, form): response = super().form_valid(form) messages.success(self.request, 'Unidad actualizada con éxito.') if self.request.headers.get('X-Requested-With') == 'XMLHttpRequest': return HttpResponse('OK') return response @method_decorator(login_required, name='dispatch') class UnidadDeleteView(DeleteView): model = Unidades template_name = 'unidad/eliminar.html' success_url = reverse_lazy('unidad_list') def delete(self, request, *args, **kwargs): self.object = self.get_object() self.object.delete() messages.success(request, 'Unidad eliminada con éxito.') if request.headers.get('X-Requested-With') == 'XMLHttpRequest': return HttpResponse('OK') return redirect(self.success_url) @login_required def eliminar_asignacion_admin(request, pk): asignacion = get_object_or_404(AsignadaUnidadAdministrativa, pk=pk) vehiculo = asignacion.vehiculo vehiculo.asignado = False # Liberar el vehículo vehiculo.save() asignacion.delete() messages.success(request, "Vehículo administrativo liberado exitosamente.") return redirect('detalle', id=asignacion.unidad.id) @login_required def eliminar_asignacion_tactica(request, pk): asignacion = get_object_or_404(AsignadaUnidadTactica, pk=pk) vehiculo = asignacion.vehiculo vehiculo.asignado = False # Liberar el vehículo vehiculo.save() asignacion.delete() messages.success(request, "Vehículo táctico liberado exitosamente.") return redirect('detalle', id=asignacion.unidad.id) # Asignación Administrativa @login_required def asignacion_administrativa(request): administrativos = AsignacionAdministrativa.objects.all() if request.method == 'POST': form = AsignacionAdministrativaForm(request.POST) if form.is_valid(): unidad = form.cleaned_data['unidad'] directores = form.cleaned_data['directores'] jefes = form.cleaned_data['jefes'] comprobante = form.cleaned_data['comprobante'] vehiculos = form.cleaned_data['vehiculos'] precios = request.POST.getlist('precios') if len(vehiculos) != len(precios): messages.error(request, "Debes ingresar un precio para cada vehículo seleccionado.") else: asignaciones = [] for vehiculo, precio in zip(vehiculos, precios): try: precio_decimal = float(precio) asignacion = AsignacionAdministrativa( unidad=unidad, directores=directores, jefes=jefes, vehiculo=vehiculo, comprobante=comprobante, precio=precio_decimal ) asignacion.save() asignaciones.append(asignacion) except ValueError: messages.error(request, f"Precio inválido para {vehiculo}: '{precio}'") break else: messages.success(request, f"{len(asignaciones)} asignación(es) registrada(s) con éxito.") if request.headers.get('X-Requested-With') == 'XMLHttpRequest': return HttpResponse('OK') return redirect('asignacion_administrativa') else: form = AsignacionAdministrativaForm() context = {'form': form, 'administrativos': administrativos} return render(request, 'asignacion_administrativa/index.html', context) @login_required def eliminar_todo(request): if request.method == 'POST': asignaciones = AsignacionAdministrativa.objects.all() for asignacion in asignaciones: # Crear registro histórico AsignadaUnidadAdministrativa.objects.create( unidad=asignacion.unidad, directores=asignacion.directores, jefes=asignacion.jefes, vehiculo=asignacion.vehiculo, fecha_creacion=asignacion.fecha_creacion, comprobante=asignacion.comprobante, precio=asignacion.precio ) # Marcar como no reasignable vehiculo = asignacion.vehiculo vehiculo.asignado = True # Mantener como asignado permanentemente vehiculo.save() AsignacionAdministrativa.objects.all().delete() messages.success(request, "Todas las asignaciones han sido archivadas.") return redirect('asignacion_administrativa') @login_required def generar_pdf(request): asignaciones = AsignacionAdministrativa.objects.all() if asignaciones.exists(): unidad = asignaciones.first().unidad director = asignaciones.first().directores jefe = asignaciones.first().jefes else: unidad = director = jefe = None html_string = render_to_string('pdf/pdf_asinacion.html', { 'asignaciones': [], 'jefe': None, 'director': None, 'unidad_nombre': "Sin Unidad", }) response = HttpResponse(content_type='application/pdf') response['Content-Disposition'] = 'attachment; filename="asignacion_administrativa.pdf"' pisa.CreatePDF(html_string, dest=response) messages.info(request, "No hay asignaciones para generar el PDF.") return response html_string = render_to_string('pdf/pdf_asinacion.html', { 'asignaciones': asignaciones, 'jefe': jefe, 'director': director, 'unidad_nombre': unidad.nombre if unidad else "Sin Unidad", }) response = HttpResponse(content_type='application/pdf') response['Content-Disposition'] = 'attachment; filename="asignacion_administrativa.pdf"' pisa_status = pisa.CreatePDF(html_string, dest=response) if pisa_status.err: return HttpResponse('Error al generar el PDF.') messages.success(request, "PDF generado con éxito.") return response # Asignación Táctica @login_required def asignacion_tactica(request): asignaciones = AsignacionTactica.objects.all() if request.method == 'POST': form = AsignacionTacticaForm(request.POST) if form.is_valid(): unidad = form.cleaned_data['unidad'] directores = form.cleaned_data['directores'] jefes = form.cleaned_data['jefes'] comprobante = form.cleaned_data['comprobante'] vehiculos = form.cleaned_data['vehiculos'] precios = request.POST.getlist('precios') if len(vehiculos) != len(precios): messages.error(request, "Debes ingresar un precio para cada vehículo seleccionado.") else: asignaciones_nuevas = [] for vehiculo, precio in zip(vehiculos, precios): try: precio_decimal = float(precio) asignacion = AsignacionTactica( unidad=unidad, directores=directores, jefes=jefes, vehiculo=vehiculo, comprobante=comprobante, precio=precio_decimal ) asignacion.save() asignaciones_nuevas.append(asignacion) except ValueError: messages.error(request, f"Precio inválido para {vehiculo}: '{precio}'") break else: messages.success(request, f"{len(asignaciones_nuevas)} asignación(es) registrada(s) con éxito.") if request.headers.get('X-Requested-With') == 'XMLHttpRequest': return HttpResponse('OK') return redirect('asignacion_tactica') else: form = AsignacionTacticaForm() context = {'form': form, 'asignaciones': asignaciones} return render(request, 'asignacion_tactica/index.html', context) @login_required def eliminar_todo_tactico(request): if request.method == 'POST': asignaciones = AsignacionTactica.objects.all() for asignacion in asignaciones: AsignadaUnidadTactica.objects.create( unidad=asignacion.unidad, directores=asignacion.directores, jefes=asignacion.jefes, vehiculo=asignacion.vehiculo, fecha_creacion=asignacion.fecha_creacion, comprobante=asignacion.comprobante, precio=asignacion.precio ) vehiculo = asignacion.vehiculo vehiculo.asignado = True # Mantener como asignado permanentemente vehiculo.save() AsignacionTactica.objects.all().delete() messages.success(request, "Todas las asignaciones tácticas han sido archivadas.") return redirect('asignacion_tactica') @login_required def generar_pdf_tactico(request): asignaciones = AsignacionTactica.objects.all() if asignaciones.exists(): unidad = asignaciones.first().unidad director = asignaciones.first().directores jefe = asignaciones.first().jefes else: unidad = director = jefe = None html_string = render_to_string('pdf/pdf_asignacion_tactica.html', { 'asignaciones': [], 'jefe': None, 'director': None, 'unidad_nombre': "Sin Unidad", }) response = HttpResponse(content_type='application/pdf') response['Content-Disposition'] = 'attachment; filename="asignacion_tactica.pdf"' pisa.CreatePDF(html_string, dest=response) messages.info(request, "No hay asignaciones para generar el PDF.") return response html_string = render_to_string('pdf/pdf_asignacion_tactica.html', { 'asignaciones': asignaciones, 'jefe': jefe, 'director': director, 'unidad_nombre': unidad.nombre if unidad else "Sin Unidad", }) response = HttpResponse(content_type='application/pdf') response['Content-Disposition'] = 'attachment; filename="asignacion_tactica.pdf"' pisa_status = pisa.CreatePDF(html_string, dest=response) if pisa_status.err: return HttpResponse('Error al generar el PDF.') messages.success(request, "PDF generado con éxito.") return response # Detalle de Unidad @login_required def detalle_unidad(request, id): unidad = get_object_or_404(Unidades, id=id) administrativo = AsignadaUnidadAdministrativa.objects.filter(unidad=id) tactico = AsignadaUnidadTactica.objects.filter(unidad=id) context = { 'unidad': unidad, 'administrativo': administrativo, 'tactico': tactico } return render(request, 'unidad/detalle.html', context) # Generar PDF por Unidad (Administrativo o Táctico) @login_required def detalle_pdf(request, tipo, unidad_id): unidad = get_object_or_404(Unidades, pk=unidad_id) jefe = Subjefes.objects.first() director = Directores.objects.first() context = { 'unidad_nombre': unidad.nombre, 'jefe': jefe, 'director': director, 'tipo': tipo, } if tipo == 'administrativo': asignaciones = AsignadaUnidadAdministrativa.objects.filter(unidad=unidad) context['asignaciones'] = asignaciones elif tipo == 'tactico': asignaciones = AsignadaUnidadTactica.objects.filter(unidad=unidad) context['asignaciones'] = asignaciones else: messages.error(request, "Tipo no válido") return HttpResponse("Tipo no válido", status=400) html = render_to_string('pdf/administrativo.html', context) response = HttpResponse(content_type='application/pdf') response['Content-Disposition'] = f'attachment; filename="reporte_{unidad.nombre}_{tipo}.pdf"' pisa_status = pisa.CreatePDF(html, dest=response, encoding='UTF-8') if pisa_status.err: messages.error(request, "Error al generar el PDF") return HttpResponse("Error al generar el PDF", status=500) messages.success(request, f"PDF de {tipo} generado con éxito para {unidad.nombre}") return response def expired_page(request): return render(request, 'expired.html', status=403)