""" 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