Files
eumi_v4/equipment/views.py
T

917 lines
29 KiB
Python

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