HH/apps/accounts/simple_acknowledgement_views.py
2026-03-09 16:10:24 +03:00

566 lines
21 KiB
Python

"""
Simple Acknowledgement Views
Simplified views for employee acknowledgement system.
"""
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.utils.translation import gettext_lazy as _
from django.utils import timezone
from django.http import HttpResponse
from django.db.models import Count, Q
from django.core.paginator import Paginator
from apps.accounts.models import User, SimpleAcknowledgement, EmployeeAcknowledgement
# ============================================================================
# EMPLOYEE VIEWS
# ============================================================================
@login_required
def simple_acknowledgement_list(request):
"""
Employee view: List all acknowledgements with sign status.
Shows acknowledgements that were sent to the employee OR are active and required.
"""
employee = request.user
# Get acknowledgements that were specifically sent to this employee
sent_ack_ids = EmployeeAcknowledgement.objects.filter(
employee=employee
).values_list('acknowledgement_id', flat=True)
# Get all active required acknowledgements that haven't been sent yet
active_required_ids = SimpleAcknowledgement.objects.filter(
is_active=True,
is_required=True
).exclude(
id__in=sent_ack_ids
).values_list('id', flat=True)
# Combine all acknowledgement IDs the employee needs to see
all_ack_ids = list(sent_ack_ids) + list(active_required_ids)
# Get all acknowledgements
acknowledgements = SimpleAcknowledgement.objects.filter(
id__in=all_ack_ids
).order_by('order', 'title')
# Get or create employee acknowledgements for each
employee_acks = []
for ack in acknowledgements:
emp_ack, created = EmployeeAcknowledgement.objects.get_or_create(
employee=employee,
acknowledgement=ack,
defaults={'is_signed': False}
)
employee_acks.append({
'acknowledgement': ack,
'signature': emp_ack,
'is_signed': emp_ack.is_signed,
'signed_at': emp_ack.signed_at,
'has_pdf': bool(emp_ack.signed_pdf),
'sent_at': emp_ack.sent_at,
'sent_by': emp_ack.sent_by,
})
# Separate pending and signed
pending_acks = [e for e in employee_acks if not e['is_signed']]
signed_acks = [e for e in employee_acks if e['is_signed']]
# Calculate stats
total = len(employee_acks)
signed = len(signed_acks)
pending = len(pending_acks)
context = {
'employee_acks': employee_acks,
'pending_acks': pending_acks,
'signed_acks': signed_acks,
'total': total,
'signed': signed,
'pending': pending,
'progress': int((signed / total * 100)) if total > 0 else 0,
}
return render(request, 'accounts/simple_acknowledgements/list.html', context)
@login_required
def simple_acknowledgement_sign(request, ack_id):
"""
Employee view: Sign an acknowledgement.
Shows the acknowledgement details and a form to sign with name and employee ID.
"""
acknowledgement = get_object_or_404(SimpleAcknowledgement, id=ack_id, is_active=True)
employee = request.user
# Get or create the employee acknowledgement record
emp_ack, created = EmployeeAcknowledgement.objects.get_or_create(
employee=employee,
acknowledgement=acknowledgement,
defaults={'is_signed': False}
)
# If already signed, redirect to list
if emp_ack.is_signed:
messages.info(request, _('You have already signed this acknowledgement.'))
return redirect('accounts:simple_acknowledgements:employee_list')
if request.method == 'POST':
signature_name = request.POST.get('signature_name', '').strip()
signature_employee_id = request.POST.get('signature_employee_id', '').strip()
confirm = request.POST.get('confirm') == 'on'
# Validation
if not signature_name:
messages.error(request, _('Please enter your name.'))
elif not signature_employee_id:
messages.error(request, _('Please enter your employee ID.'))
elif not confirm:
messages.error(request, _('Please confirm that you have read and understood the acknowledgement.'))
else:
# Save the signature
emp_ack.is_signed = True
emp_ack.signed_at = timezone.now()
emp_ack.signature_name = signature_name
emp_ack.signature_employee_id = signature_employee_id
emp_ack.ip_address = get_client_ip(request)
emp_ack.user_agent = request.META.get('HTTP_USER_AGENT', '')[:500]
emp_ack.save()
# Generate simple signed PDF (optional - can be added later)
# For now, just copy the original PDF if it exists
if acknowledgement.pdf_document and not emp_ack.signed_pdf:
emp_ack.signed_pdf = acknowledgement.pdf_document
emp_ack.save()
messages.success(request, _('Acknowledgement signed successfully!'))
return redirect('accounts:simple_acknowledgements:employee_list')
context = {
'acknowledgement': acknowledgement,
'emp_ack': emp_ack,
'employee': employee,
}
return render(request, 'accounts/simple_acknowledgements/sign.html', context)
@login_required
def simple_acknowledgement_download(request, ack_id):
"""
Employee view: Download the signed PDF.
"""
emp_ack = get_object_or_404(
EmployeeAcknowledgement,
acknowledgement_id=ack_id,
employee=request.user,
is_signed=True
)
if emp_ack.signed_pdf:
response = HttpResponse(emp_ack.signed_pdf, content_type='application/pdf')
response['Content-Disposition'] = f'attachment; filename="{emp_ack.acknowledgement.title}.pdf"'
return response
elif emp_ack.acknowledgement.pdf_document:
response = HttpResponse(emp_ack.acknowledgement.pdf_document, content_type='application/pdf')
response['Content-Disposition'] = f'attachment; filename="{emp_ack.acknowledgement.title}.pdf"'
return response
else:
messages.error(request, _('No PDF available for this acknowledgement.'))
return redirect('accounts:simple_acknowledgements:employee_list')
# ============================================================================
# ADMIN VIEWS
# ============================================================================
@login_required
def admin_acknowledgement_list(request):
"""
Admin view: List all acknowledgements with management options.
"""
if not request.user.is_staff and not request.user.is_px_admin():
messages.error(request, _('You do not have permission to access this page.'))
return redirect('accounts:simple_acknowledgements:employee_list')
acknowledgements = SimpleAcknowledgement.objects.all().order_by('order', 'title')
context = {
'acknowledgements': acknowledgements,
}
return render(request, 'accounts/simple_acknowledgements/admin_list.html', context)
@login_required
def admin_acknowledgement_create(request):
"""
Admin view: Create a new acknowledgement.
"""
if not request.user.is_staff and not request.user.is_px_admin():
messages.error(request, _('You do not have permission to access this page.'))
return redirect('accounts:simple_acknowledgements:employee_list')
if request.method == 'POST':
title = request.POST.get('title', '').strip()
description = request.POST.get('description', '').strip()
pdf_document = request.FILES.get('pdf_document')
is_required = request.POST.get('is_required') == 'on'
is_active = request.POST.get('is_active') == 'on'
order = request.POST.get('order', '0')
if not title:
messages.error(request, _('Please enter a title.'))
else:
acknowledgement = SimpleAcknowledgement.objects.create(
title=title,
description=description,
pdf_document=pdf_document,
is_required=is_required,
is_active=is_active,
order=int(order) if order.isdigit() else 0,
)
messages.success(request, _('Acknowledgement created successfully!'))
return redirect('accounts:simple_acknowledgements:admin_list')
return render(request, 'accounts/simple_acknowledgements/admin_create.html')
@login_required
def admin_acknowledgement_edit(request, ack_id):
"""
Admin view: Edit an existing acknowledgement.
"""
if not request.user.is_staff and not request.user.is_px_admin():
messages.error(request, _('You do not have permission to access this page.'))
return redirect('accounts:simple_acknowledgements:employee_list')
acknowledgement = get_object_or_404(SimpleAcknowledgement, id=ack_id)
if request.method == 'POST':
title = request.POST.get('title', '').strip()
description = request.POST.get('description', '').strip()
pdf_document = request.FILES.get('pdf_document')
is_required = request.POST.get('is_required') == 'on'
is_active = request.POST.get('is_active') == 'on'
order = request.POST.get('order', '0')
if not title:
messages.error(request, _('Please enter a title.'))
else:
acknowledgement.title = title
acknowledgement.description = description
acknowledgement.is_required = is_required
acknowledgement.is_active = is_active
acknowledgement.order = int(order) if order.isdigit() else 0
if pdf_document:
acknowledgement.pdf_document = pdf_document
acknowledgement.save()
messages.success(request, _('Acknowledgement updated successfully!'))
return redirect('accounts:simple_acknowledgements:admin_list')
context = {
'acknowledgement': acknowledgement,
'action': 'edit',
}
return render(request, 'accounts/simple_acknowledgements/admin_form.html', context)
@login_required
def admin_acknowledgement_delete(request, ack_id):
"""
Admin view: Delete an acknowledgement.
"""
if not request.user.is_staff and not request.user.is_px_admin():
messages.error(request, _('You do not have permission to access this page.'))
return redirect('accounts:simple_acknowledgements:employee_list')
acknowledgement = get_object_or_404(SimpleAcknowledgement, id=ack_id)
if request.method == 'POST':
acknowledgement.delete()
messages.success(request, _('Acknowledgement deleted successfully!'))
return redirect('accounts:simple_acknowledgements:admin_list')
context = {
'acknowledgement': acknowledgement,
}
return render(request, 'accounts/simple_acknowledgements/admin_delete.html', context)
@login_required
def admin_employee_signatures(request, ack_id=None):
"""
Admin view: View all employee signatures.
Can filter by acknowledgement if ack_id is provided.
"""
if not request.user.is_staff and not request.user.is_px_admin():
messages.error(request, _('You do not have permission to access this page.'))
return redirect('accounts:simple_acknowledgements:employee_list')
signatures = EmployeeAcknowledgement.objects.select_related(
'employee', 'acknowledgement'
).order_by('-signed_at')
if ack_id:
acknowledgement = get_object_or_404(SimpleAcknowledgement, id=ack_id)
signatures = signatures.filter(acknowledgement=acknowledgement)
else:
acknowledgement = None
# Search
search = request.GET.get('search', '')
if search:
signatures = signatures.filter(
Q(employee__email__icontains=search) |
Q(employee__first_name__icontains=search) |
Q(employee__last_name__icontains=search) |
Q(signature_name__icontains=search) |
Q(signature_employee_id__icontains=search)
)
# Filter by status
status_filter = request.GET.get('status', '')
if status_filter == 'signed':
signatures = signatures.filter(is_signed=True)
elif status_filter == 'pending':
signatures = signatures.filter(is_signed=False)
# Pagination
paginator = Paginator(signatures, 50)
page_number = request.GET.get('page')
page_obj = paginator.get_page(page_number)
context = {
'signatures': page_obj,
'acknowledgement': acknowledgement,
'search': search,
'status_filter': status_filter,
}
return render(request, 'accounts/simple_acknowledgements/admin_signatures.html', context)
@login_required
def admin_export_signatures(request):
"""
Admin view: Export all signatures to CSV.
"""
if not request.user.is_staff and not request.user.is_px_admin():
messages.error(request, _('You do not have permission to access this page.'))
return redirect('accounts:simple_acknowledgements:employee_list')
import csv
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="acknowledgement_signatures.csv"'
writer = csv.writer(response)
writer.writerow([
'Employee Email', 'Employee Name', 'Employee ID (at sign)',
'Acknowledgement', 'Status', 'Signed At', 'IP Address'
])
signatures = EmployeeAcknowledgement.objects.select_related(
'employee', 'acknowledgement'
).order_by('-signed_at')
for sig in signatures:
writer.writerow([
sig.employee.email,
sig.signature_name or sig.employee.get_full_name(),
sig.signature_employee_id,
sig.acknowledgement.title,
'Signed' if sig.is_signed else 'Pending',
sig.signed_at.strftime('%Y-%m-%d %H:%M') if sig.signed_at else '',
sig.ip_address or '',
])
return response
@login_required
def admin_send_acknowledgement(request, ack_id):
"""
Admin view: Send acknowledgement to specific staff members.
"""
if not request.user.is_staff and not request.user.is_px_admin():
messages.error(request, _('You do not have permission to access this page.'))
return redirect('accounts:simple_acknowledgements:employee_list')
acknowledgement = get_object_or_404(SimpleAcknowledgement, id=ack_id)
# Get all active staff
from apps.accounts.models import User
staff_list = User.objects.filter(
is_active=True,
is_provisional=False
).exclude(
id=request.user.id
).order_by('first_name', 'last_name')
# Get already sent employees
already_sent_ids = EmployeeAcknowledgement.objects.filter(
acknowledgement=acknowledgement
).values_list('employee_id', flat=True)
if request.method == 'POST':
employee_ids = request.POST.getlist('employees')
send_email = request.POST.get('send_email') == 'on'
if not employee_ids:
messages.error(request, _('Please select at least one employee.'))
else:
sent_count = 0
for emp_id in employee_ids:
emp_ack, created = EmployeeAcknowledgement.objects.get_or_create(
employee_id=emp_id,
acknowledgement=acknowledgement,
defaults={
'sent_at': timezone.now(),
'sent_by': request.user,
}
)
if created:
sent_count += 1
# Update sent info if record already existed but wasn't sent
elif not emp_ack.sent_at:
emp_ack.sent_at = timezone.now()
emp_ack.sent_by = request.user
emp_ack.save()
sent_count += 1
# Send email notifications if requested
if send_email and sent_count > 0:
try:
from apps.notifications.services import NotificationService
employees = User.objects.filter(id__in=employee_ids)
for emp in employees:
NotificationService.send_email(
email=emp.email,
subject=f'Action Required: {acknowledgement.title}',
message=f'''Dear {emp.get_full_name() or emp.email},
You have been requested to sign the following acknowledgement: {acknowledgement.title}
Please log in to the system to review and sign this document.
Best regards,
HR Department''',
related_object=emp_ack,
)
except Exception as e:
messages.warning(request, _('Acknowledgements sent but email notification failed.'))
messages.success(request, _('Acknowledgement sent to {count} employee(s).').format(count=sent_count))
return redirect('accounts:simple_acknowledgements:admin_list')
context = {
'acknowledgement': acknowledgement,
'staff_list': staff_list,
'already_sent_ids': list(already_sent_ids),
}
return render(request, 'accounts/simple_acknowledgements/admin_send.html', context)
@login_required
def admin_upload_signed_pdf(request, signature_id):
"""
Admin view: Upload signed PDF on behalf of an employee.
"""
if not request.user.is_staff and not request.user.is_px_admin():
messages.error(request, _('You do not have permission to access this page.'))
return redirect('accounts:simple_acknowledgements:employee_list')
emp_ack = get_object_or_404(
EmployeeAcknowledgement.objects.select_related('employee', 'acknowledgement'),
id=signature_id
)
if request.method == 'POST':
signed_pdf = request.FILES.get('signed_pdf')
signature_name = request.POST.get('signature_name', emp_ack.employee.get_full_name())
signature_employee_id = request.POST.get('signature_employee_id', '')
notes = request.POST.get('notes', '')
if not signed_pdf:
messages.error(request, _('Please upload a signed PDF.'))
else:
emp_ack.signed_pdf = signed_pdf
emp_ack.is_signed = True
emp_ack.signed_at = timezone.now()
emp_ack.signature_name = signature_name
emp_ack.signature_employee_id = signature_employee_id
emp_ack.notes = notes
emp_ack.save()
messages.success(
request,
_('Signed PDF uploaded successfully for {name}.').format(name=emp_ack.employee.get_full_name())
)
return redirect('accounts:simple_acknowledgements:admin_signatures')
context = {
'emp_ack': emp_ack,
'employee': emp_ack.employee,
'acknowledgement': emp_ack.acknowledgement,
}
return render(request, 'accounts/simple_acknowledgements/admin_upload_pdf.html', context)
@login_required
def admin_mark_as_sent(request, ack_id):
"""
Admin view: Mark acknowledgement as sent to all employees (bulk action).
"""
if not request.user.is_staff and not request.user.is_px_admin():
messages.error(request, _('You do not have permission to access this page.'))
return redirect('accounts:simple_acknowledgements:employee_list')
acknowledgement = get_object_or_404(SimpleAcknowledgement, id=ack_id)
if request.method == 'POST':
from apps.accounts.models import User
# Get all active employees who haven't been sent this yet
all_employees = User.objects.filter(is_active=True, is_provisional=False)
already_sent = EmployeeAcknowledgement.objects.filter(
acknowledgement=acknowledgement
).values_list('employee_id', flat=True)
new_employees = all_employees.exclude(id__in=already_sent)
created_count = 0
for emp in new_employees:
EmployeeAcknowledgement.objects.create(
employee=emp,
acknowledgement=acknowledgement,
sent_at=timezone.now(),
sent_by=request.user,
)
created_count += 1
messages.success(
request,
_('Acknowledgement sent to {count} new employee(s).').format(count=created_count)
)
return redirect('accounts:simple_acknowledgements:admin_list')
return redirect('accounts:simple_acknowledgements:admin_list')
# ============================================================================
# HELPER FUNCTIONS
# ============================================================================
def get_client_ip(request):
"""Get client IP address from request."""
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0]
else:
ip = request.META.get('REMOTE_ADDR')
return ip