from django.contrib.staticfiles import finders from django.template.loader import get_template from xhtml2pdf import pisa from django.contrib.auth.decorators import login_required from django.urls import reverse from django.http import HttpResponse from django.db.models import Q from django.shortcuts import render, get_object_or_404 from users.models import Employee from .models import Computer, Monitor, Peripheral, MaintenanceRecord, HandoverProtocol from .forms import ( ComputerForm, MonitorForm, PeripheralForm, MaintenanceRecordForm, HandoverProtocolForm, ) from django.core.exceptions import PermissionDenied from itertools import chain from operator import attrgetter from django.template.loader import get_template from django.http import HttpResponse from .forms import DecommissionRecordForm from .models import DecommissionRecord from datetime import date, timedelta from .models import DigitalCertificate from .forms import DigitalCertificateForm import csv @login_required def computer_maintenance_create(request, pk): computer = get_object_or_404(Computer, pk=pk) if request.method == "POST": # 1. Creăm instanța și îi legăm computerul DE LA ÎNCEPUT maintenance_instance = MaintenanceRecord(computer=computer) # 2. Pasăm instanța către formular pentru a trece de validarea din clean() form = MaintenanceRecordForm(request.POST, instance=maintenance_instance) if form.is_valid(): form.save() # Acum se va salva perfect! # Returnăm status 204 și instruim HTMX să reîncarce complet pagina response = HttpResponse(status=204) response["HX-Refresh"] = "true" return response else: initial_name = ( request.user.get_full_name() or request.user.username if request.user.is_authenticated else "" ) form = MaintenanceRecordForm(initial={"performed_by": initial_name}) return render( request, "equipment/partials/maintenance_form.html", { "form": form, "equipment_title": computer.equipment_code, "post_url": reverse( "equipment:computer_maintenance_create", args=[computer.pk] ), }, ) @login_required def print_inventory_sheet(request, employee_id): employee = get_object_or_404(Employee, id=employee_id) inventory = employee.get_active_inventory context = { "employee": employee, "inventory": inventory, } return render(request, "equipment/print_inventory.html", context) @login_required def print_handover_protocol(request, protocol_id): protocol = get_object_or_404(HandoverProtocol, id=protocol_id) context = { "protocol": protocol, } return render(request, "equipment/print_handover.html", context) @login_required def equipment_list(request): query = request.GET.get("search", "") # Fetch only active equipment computers = Computer.objects.filter(status="activ").order_by("-entry_date") monitors = Monitor.objects.filter(status="activ").order_by("-entry_date") peripherals = Peripheral.objects.filter(status="activ").order_by("-entry_date") if query: computers = computers.filter( Q(equipment_code__icontains=query) | Q(oem_name__icontains=query) | Q(motherboard__icontains=query) | Q(cpu__icontains=query) | Q(ram__icontains=query) | Q(storage__icontains=query) | Q(gpu__icontains=query) | Q(mac_address__icontains=query) | Q(os_license__icontains=query) | Q(observations__icontains=query) | Q(assigned_to__name__icontains=query) ) monitors = monitors.filter( Q(equipment_code__icontains=query) | Q(name__icontains=query) | Q(serial_number__icontains=query) | Q(screen_diagonal__icontains=query) | Q(observations__icontains=query) | Q(assigned_to__name__icontains=query) ) peripherals = peripherals.filter( Q(equipment_code__icontains=query) | Q(name__icontains=query) | Q(peripheral_type__icontains=query) | Q(serial_number__icontains=query) | Q(observations__icontains=query) | Q(assigned_to__name__icontains=query) ) context = { "computers": computers, "monitors": monitors, "peripherals": peripherals, } if request.headers.get("HX-Request"): return render(request, "equipment/partials/equipment_tables.html", context) return render(request, "equipment/equipment_list.html", context) @login_required def computer_form(request, pk=None): # Doar personalul IT (staff) are voie să adauge/modifice if not request.user.is_staff: raise PermissionDenied("Nu aveți permisiunea de a edita echipamente.") if pk: computer = get_object_or_404(Computer, pk=pk) else: computer = None if request.method == "POST": form = ComputerForm(request.POST, instance=computer) if form.is_valid(): form.save() # Return 204 and trigger the table refresh and modal close response = HttpResponse(status=204) response["HX-Trigger"] = "equipmentListChanged, closeModal" return response else: form = ComputerForm(instance=computer) return render( request, "equipment/partials/computer_form.html", {"form": form, "computer": computer}, ) @login_required def monitor_form(request, pk=None): if pk: monitor = get_object_or_404(Monitor, pk=pk) else: monitor = None if request.method == "POST": form = MonitorForm(request.POST, instance=monitor) if form.is_valid(): form.save() response = HttpResponse(status=204) response["HX-Trigger"] = "equipmentListChanged, closeModal" return response else: form = MonitorForm(instance=monitor) return render( request, "equipment/partials/monitor_form.html", {"form": form, "monitor": monitor}, ) @login_required def peripheral_form(request, pk=None): if pk: peripheral = get_object_or_404(Peripheral, pk=pk) else: peripheral = None if request.method == "POST": form = PeripheralForm(request.POST, instance=peripheral) if form.is_valid(): form.save() response = HttpResponse(status=204) response["HX-Trigger"] = "equipmentListChanged, closeModal" return response else: form = PeripheralForm(instance=peripheral) return render( request, "equipment/partials/peripheral_form.html", {"form": form, "peripheral": peripheral}, ) @login_required def computer_technical_sheet(request, pk): computer = get_object_or_404(Computer, pk=pk) # Fetch maintenance logs (using the related_name from your models) maintenance_logs = computer.maintenance_logs.all().order_by("-date") # Calculate hardware changes from simple_history history = list(computer.history.all()) hardware_changes = [] tracked_fields = { "cpu": "Procesor", "ram": "Memorie RAM", "storage": "Stocare", "gpu": "Placă Video", "motherboard": "Placă de bază", "status": "Status", } # Compare each historical record with the one that came before it for i in range(len(history) - 1): new_rec = history[i] old_rec = history[i + 1] for field, label in tracked_fields.items(): old_val = getattr(old_rec, field) new_val = getattr(new_rec, field) if old_val != new_val: hardware_changes.append( { "date": new_rec.history_date, "field": label, "old": old_val, "new": new_val, } ) # --- Extragere istoric posesie --- ownership_history = [] # Parcurgem cronologic (de la cea mai veche la cea mai nouă înregistrare) history_asc = list(computer.history.all().order_by("history_date")) current_owner = None start_date = None for record in history_asc: owner = record.assigned_to # Dacă s-a schimbat posesorul if owner != current_owner: # Salvăm perioada precedentă if start_date is not None: ownership_history.append( { "owner": ( current_owner.name if current_owner else "Magazie (Neasignat)" ), "start_date": start_date, "end_date": record.history_date, } ) # Începem perioada nouă current_owner = owner start_date = record.history_date # Adăugăm perioada curentă, care e încă în desfășurare if start_date is not None: ownership_history.append( { "owner": current_owner.name if current_owner else "Magazie (Neasignat)", "start_date": start_date, "end_date": None, # Semnifică 'Prezent' } ) # Inversăm lista pentru a avea cel mai recent posesor sus ownership_history.reverse() # ------------------------------------------ return render( request, "equipment/technical_sheet.html", { "computer": computer, "maintenance_logs": maintenance_logs, "hardware_changes": hardware_changes, "ownership_history": ownership_history, }, ) @login_required def monitor_technical_sheet(request, pk): monitor = get_object_or_404(Monitor, pk=pk) maintenance_logs = monitor.maintenance_logs.all().order_by("-date") # --- Extragere istoric posesie --- ownership_history = [] history_asc = list(monitor.history.all().order_by("history_date")) current_owner = None start_date = None for record in history_asc: owner = record.assigned_to if owner != current_owner: if start_date is not None: ownership_history.append( { "owner": ( current_owner.name if current_owner else "Magazie (Neasignat)" ), "start_date": start_date, "end_date": record.history_date, } ) current_owner = owner start_date = record.history_date if start_date is not None: ownership_history.append( { "owner": current_owner.name if current_owner else "Magazie (Neasignat)", "start_date": start_date, "end_date": None, } ) ownership_history.reverse() # --------------------------------- return render( request, "equipment/monitor_technical_sheet.html", { "monitor": monitor, "maintenance_logs": maintenance_logs, "ownership_history": ownership_history, # Trimitem variabila către HTML }, ) @login_required def monitor_maintenance_create(request, pk): monitor = get_object_or_404(Monitor, pk=pk) if request.method == "POST": maintenance_instance = MaintenanceRecord(monitor=monitor) form = MaintenanceRecordForm(request.POST, instance=maintenance_instance) if form.is_valid(): form.save() response = HttpResponse(status=204) response["HX-Refresh"] = "true" return response else: initial_name = ( request.user.get_full_name() or request.user.username if request.user.is_authenticated else "" ) form = MaintenanceRecordForm(initial={"performed_by": initial_name}) return render( request, "equipment/partials/maintenance_form.html", { "form": form, "equipment_title": monitor.equipment_code, "post_url": reverse( "equipment:monitor_maintenance_create", args=[monitor.pk] ), }, ) @login_required @login_required def peripheral_technical_sheet(request, pk): peripheral = get_object_or_404(Peripheral, pk=pk) maintenance_logs = peripheral.maintenance_logs.all().order_by("-date") # --- Extragere istoric posesie --- ownership_history = [] history_asc = list(peripheral.history.all().order_by("history_date")) current_owner = None start_date = None for record in history_asc: owner = record.assigned_to if owner != current_owner: if start_date is not None: ownership_history.append( { "owner": ( current_owner.name if current_owner else "Magazie (Neasignat)" ), "start_date": start_date, "end_date": record.history_date, } ) current_owner = owner start_date = record.history_date if start_date is not None: ownership_history.append( { "owner": current_owner.name if current_owner else "Magazie (Neasignat)", "start_date": start_date, "end_date": None, } ) ownership_history.reverse() # --------------------------------- return render( request, "equipment/peripheral_technical_sheet.html", { "peripheral": peripheral, "maintenance_logs": maintenance_logs, "ownership_history": ownership_history, # Trimitem variabila către HTML }, ) @login_required def peripheral_maintenance_create(request, pk): peripheral = get_object_or_404(Peripheral, pk=pk) if request.method == "POST": maintenance_instance = MaintenanceRecord(peripheral=peripheral) form = MaintenanceRecordForm(request.POST, instance=maintenance_instance) if form.is_valid(): form.save() response = HttpResponse(status=204) response["HX-Refresh"] = "true" return response else: initial_name = ( request.user.get_full_name() or request.user.username if request.user.is_authenticated else "" ) form = MaintenanceRecordForm(initial={"performed_by": initial_name}) return render( request, "equipment/partials/maintenance_form.html", { "form": form, "equipment_title": peripheral.equipment_code, "post_url": reverse( "equipment:peripheral_maintenance_create", args=[peripheral.pk] ), }, ) @login_required def handover_protocol_list(request): # Preluăm toate procesele verbale, ordonate de la cel mai nou la cel mai vechi protocols = HandoverProtocol.objects.all().order_by("-date", "-document_number") return render(request, "equipment/handover_list.html", {"protocols": protocols}) @login_required def handover_protocol_form(request, pk=None): # --- Restricție de securitate --- if not request.user.is_staff: raise PermissionDenied( "Nu aveți permisiunea de a genera procese verbale de predare-primire." ) if pk: protocol = get_object_or_404(HandoverProtocol, pk=pk) else: protocol = None if request.method == "POST": # ATENȚIE: request.FILES este obligatoriu pentru a prelua documentul PDF/Scanat form = HandoverProtocolForm(request.POST, request.FILES, instance=protocol) if form.is_valid(): protocol_instance = form.save(commit=False) # Completăm automat "Întocmit de (IT)" cu numele utilizatorului logat if not protocol_instance.it_validator and request.user.is_authenticated: protocol_instance.it_validator = ( request.user.get_full_name() or request.user.username ) protocol_instance.save() # ATENȚIE: Pentru câmpurile de tip SelectMultiple (Many-to-Many), # trebuie apelată manual această funcție după salvarea instanței form.save_m2m() # Preluăm noul posesor din formular new_owner = protocol_instance.new_holder # Trecem toate echipamentele selectate în proprietatea noului posesor if new_owner: # Folosim .save() în loc de .update() pentru ca simple_history să înregistreze modificarea for pc in protocol_instance.computers.all(): pc.assigned_to = new_owner pc.save() for mon in protocol_instance.monitors.all(): mon.assigned_to = new_owner mon.save() for per in protocol_instance.peripherals.all(): per.assigned_to = new_owner per.save() response = HttpResponse(status=204) response["HX-Refresh"] = "true" return response else: form = HandoverProtocolForm(instance=protocol) return render( request, "equipment/partials/handover_form.html", {"form": form, "protocol": protocol}, ) @login_required def dashboard_view(request): # --- Coloana 1: Sumar Inventar (Înlocuitor temporar pt. Tichete) --- total_computers = Computer.objects.count() unassigned_computers = Computer.objects.filter(assigned_to__isnull=True) unassigned_monitors = Monitor.objects.filter(assigned_to__isnull=True) # --- Coloana 2: Acțiuni Executate (Istoric simple_history) --- # Preluăm ultimele 10 acțiuni din fiecare categorie pc_hist = Computer.history.all()[:10] mon_hist = Monitor.history.all()[:10] per_hist = Peripheral.history.all()[:10] # Combinăm listele și le sortăm descrescător după data acțiunii (history_date) activity_log = sorted( chain(pc_hist, mon_hist, per_hist), key=attrgetter("history_date"), reverse=True )[ :10 ] # Păstrăm doar ultimele 10 cele mai recente acțiuni în total # --- Coloana 3: Mentenanțe Efectuate --- recent_maintenance = MaintenanceRecord.objects.all().order_by("-date", "-id")[:10] context = { "total_computers": total_computers, "unassigned_computers": unassigned_computers, "unassigned_monitors": unassigned_monitors, "activity_log": activity_log, "recent_maintenance": recent_maintenance, } return render(request, "equipment/dashboard.html", context) @login_required def generate_handover_pdf(request, pk): protocol = get_object_or_404(HandoverProtocol, pk=pk) # Lăsăm Django să găsească calea absolută a fonturilor pe disc font_regular = finders.find("fonts/LiberationSerif-Regular.ttf") font_bold = finders.find("fonts/LiberationSerif-Bold.ttf") # Găsim calea către siglă logo_path = finders.find("img/stema.png") # Definim contextul care va fi trimis către șablonul HTML context = { "protocol": protocol, "font_regular": font_regular, "font_bold": font_bold, "logo_path": logo_path, # Trimitem calea siglei în context # Preluăm numele complet (dacă e setat în admin), altfel luăm username-ul "generated_by": request.user.get_full_name() or request.user.username, } template = get_template("equipment/pdf_handover.html") html = template.render(context) # --- REZOLVARE DIACRITICE --- # Înlocuim "ș" și "ț" cu virgulă (standard nou) cu variantele cu sedilă (standard vechi) # pentru a fi recunoscute de fontul din 2010 și librăria PDF. html = html.replace("ș", "ş").replace("ț", "ţ") html = html.replace("Ș", "Ş").replace("Ț", "Ţ") # ---------------------------- # Creăm răspunsul HTTP de tip PDF response = HttpResponse(content_type="application/pdf") # Setăm numele fișierului descărcat response["Content-Disposition"] = ( f'attachment; filename="Proces_Verbal_Nr_{protocol.document_number}.pdf"' ) # Generăm PDF-ul pisa_status = pisa.CreatePDF(html, dest=response) if pisa_status.err: return HttpResponse("A apărut o eroare la generarea PDF-ului", status=500) return response @login_required def load_user_equipment(request): user_id = request.GET.get("previous_holder") form = HandoverProtocolForm() if user_id: # Filtrăm doar echipamentele active ale utilizatorului selectat form.fields["computers"].queryset = Computer.objects.filter( assigned_to_id=user_id, status="activ" ) form.fields["monitors"].queryset = Monitor.objects.filter( assigned_to_id=user_id, status="activ" ) form.fields["peripherals"].queryset = Peripheral.objects.filter( assigned_to_id=user_id, status="activ" ) else: form.fields["computers"].queryset = Computer.objects.none() form.fields["monitors"].queryset = Monitor.objects.none() form.fields["peripherals"].queryset = Peripheral.objects.none() # Trimitem doar bucata HTML cu cele 3 liste return render( request, "equipment/partials/equipment_dropdowns.html", {"form": form} ) @login_required def decommission_list(request): # Listăm toate procesele verbale de casare records = DecommissionRecord.objects.all().order_by("-date", "-document_number") return render(request, "equipment/decommission_list.html", {"records": records}) @login_required def decommission_form(request, pk=None): # --- Restricție de securitate --- if not request.user.is_staff: raise PermissionDenied("Nu aveți permisiunea de a gestiona casările.") if pk: record = get_object_or_404(DecommissionRecord, pk=pk) else: record = None if request.method == "POST": form = DecommissionRecordForm(request.POST, instance=record) if form.is_valid(): decommission_instance = form.save() # --- ACTUALIZARE AUTOMATĂ A INVENTARULUI --- # Trecem toate echipamentele selectate pe status "casat" și le scoatem de la utilizatori for pc in decommission_instance.computers.all(): pc.status = "casat" pc.assigned_to = None pc.save() for mon in decommission_instance.monitors.all(): mon.status = "casat" mon.assigned_to = None mon.save() for per in decommission_instance.peripherals.all(): per.status = "casat" per.assigned_to = None per.save() # ------------------------------------------- response = HttpResponse(status=204) response["HX-Refresh"] = "true" return response else: form = DecommissionRecordForm(instance=record) return render( request, "equipment/partials/decommission_form.html", {"form": form, "record": record}, ) @login_required def generate_decommission_pdf(request, pk): record = get_object_or_404(DecommissionRecord, pk=pk) font_regular = finders.find("fonts/LiberationSerif-Regular.ttf") font_bold = finders.find("fonts/LiberationSerif-Bold.ttf") logo_path = finders.find("img/stema.png") context = { "record": record, "font_regular": font_regular, "font_bold": font_bold, "logo_path": logo_path, "generated_by": request.user.get_full_name() or request.user.username, } template = get_template("equipment/pdf_decommission.html") html = template.render(context) # Rezolvare diacritice pentru fontul vechi html = html.replace("ș", "ş").replace("ț", "ţ") html = html.replace("Ș", "Ş").replace("Ț", "Ţ") response = HttpResponse(content_type="application/pdf") response["Content-Disposition"] = ( f'attachment; filename="PV_Casare_{record.document_number}.pdf"' ) pisa_status = pisa.CreatePDF(html, dest=response) if pisa_status.err: return HttpResponse("A apărut o eroare la generarea PDF-ului", status=500) return response @login_required def certificate_list(request): certificates = DigitalCertificate.objects.all().order_by("expiration_date") today = date.today() warning_date = today + timedelta(days=30) # Calculăm statusul în timp real pentru fiecare certificat for cert in certificates: if cert.is_canceled: cert.status_color = "secondary" cert.status_text = "Anulat" elif cert.expiration_date < today: cert.status_color = "danger" cert.status_text = "Expirat" elif cert.expiration_date <= warning_date: cert.status_color = "warning text-dark" cert.status_text = "Expiră curând" else: cert.status_color = "success" cert.status_text = "Activ" return render( request, "equipment/certificate_list.html", {"certificates": certificates} ) @login_required def certificate_form(request, pk=None): # --- Restricție de securitate --- if not request.user.is_staff: raise PermissionDenied( "Nu aveți permisiunea de a adăuga sau edita certificate digitale." ) if pk: certificate = get_object_or_404(DigitalCertificate, pk=pk) else: certificate = None if request.method == "POST": form = DigitalCertificateForm(request.POST, instance=certificate) if form.is_valid(): form.save() response = HttpResponse(status=204) response["HX-Refresh"] = "true" return response else: form = DigitalCertificateForm(instance=certificate) return render( request, "equipment/partials/certificate_form.html", {"form": form, "certificate": certificate}, ) @login_required def export_inventory_csv(request): response = HttpResponse(content_type="text/csv; charset=utf-8-sig") response["Content-Disposition"] = 'attachment; filename="Inventar_Echipamente.csv"' writer = csv.writer(response, delimiter=";") # ========================================== # 1. SECȚIUNEA COMPUTERE # ========================================== writer.writerow(["--- COMPUTERE ---"]) writer.writerow( [ "Cod Inventar", "OEM / Model", "Placă de bază", "Procesor (CPU)", "Memorie RAM", "Stocare", "Adresă MAC", "Utilizator Curent", "Status", ] ) computers = Computer.objects.filter(status="activ").order_by("assigned_to__name") for pc in computers: owner = pc.assigned_to.name if pc.assigned_to else "Magazie (Neasignat)" writer.writerow( [ pc.equipment_code, pc.oem_name or "Asamblat", pc.motherboard, pc.cpu, pc.ram, pc.storage, pc.mac_address, owner, pc.get_status_display(), ] ) writer.writerow([]) # Rând liber pentru spațiere writer.writerow([]) # ========================================== # 2. SECȚIUNEA MONITOARE # ========================================== writer.writerow(["--- MONITOARE ---"]) writer.writerow( [ "Cod Inventar", "Model / Nume", "Diagonală", "Număr Serial", "Utilizator Curent", "Status", ] ) monitors = Monitor.objects.filter(status="activ").order_by("assigned_to__name") for mon in monitors: owner = mon.assigned_to.name if mon.assigned_to else "Magazie (Neasignat)" writer.writerow( [ mon.equipment_code, mon.name, mon.screen_diagonal, mon.serial_number, owner, mon.get_status_display(), ] ) writer.writerow([]) # Rând liber pentru spațiere writer.writerow([]) # ========================================== # 3. SECȚIUNEA PERIFERICE # ========================================== writer.writerow(["--- PERIFERICE ---"]) writer.writerow( [ "Cod Inventar", "Tip Echipament", "Model / Nume", "Număr Serial", "Utilizator Curent", "Status", ] ) peripherals = Peripheral.objects.filter(status="activ").order_by( "assigned_to__name" ) for per in peripherals: owner = per.assigned_to.name if per.assigned_to else "Magazie (Neasignat)" writer.writerow( [ per.equipment_code, per.get_peripheral_type_display(), per.name, per.serial_number, owner, per.get_status_display(), ] ) return response