558 lines
23 KiB
HTML
558 lines
23 KiB
HTML
{% extends "base.html" %}
|
|
{% load static %}
|
|
|
|
{% block title %}Quality Audits - Quality Management{% endblock %}
|
|
|
|
{% block content %}
|
|
<!-- BEGIN breadcrumb -->
|
|
<ol class="breadcrumb float-xl-end">
|
|
<li class="breadcrumb-item"><a href="{% url 'core:dashboard' %}">Dashboard</a></li>
|
|
<li class="breadcrumb-item"><a href="{% url 'quality:dashboard' %}">Quality</a></li>
|
|
<li class="breadcrumb-item active">Audits</li>
|
|
</ol>
|
|
<!-- END breadcrumb -->
|
|
|
|
<!-- BEGIN page-header -->
|
|
<h1 class="page-header">
|
|
Quality Audits
|
|
<small>Internal and external quality audits</small>
|
|
</h1>
|
|
<!-- END page-header -->
|
|
|
|
<!-- BEGIN row -->
|
|
<div class="row">
|
|
<div class="col-xl-3 col-md-6">
|
|
<div class="widget widget-stats bg-blue">
|
|
<div class="stats-icon"><i class="fa fa-clipboard-check"></i></div>
|
|
<div class="stats-info">
|
|
<h4>TOTAL AUDITS</h4>
|
|
<p id="totalAudits">{{ total_audits|default:0 }}</p>
|
|
</div>
|
|
<div class="stats-link">
|
|
<a href="javascript:;" onclick="filterByStatus('all')">View All <i class="fa fa-arrow-alt-circle-right"></i></a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-xl-3 col-md-6">
|
|
<div class="widget widget-stats bg-info">
|
|
<div class="stats-icon"><i class="fa fa-play-circle"></i></div>
|
|
<div class="stats-info">
|
|
<h4>IN PROGRESS</h4>
|
|
<p id="inProgressAudits">{{ in_progress_audits|default:0 }}</p>
|
|
</div>
|
|
<div class="stats-link">
|
|
<a href="javascript:;" onclick="filterByStatus('in_progress')">View In Progress <i class="fa fa-arrow-alt-circle-right"></i></a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-xl-3 col-md-6">
|
|
<div class="widget widget-stats bg-orange">
|
|
<div class="stats-icon"><i class="fa fa-exclamation-triangle"></i></div>
|
|
<div class="stats-info">
|
|
<h4>FINDINGS</h4>
|
|
<p id="auditFindings">{{ audit_findings|default:0 }}</p>
|
|
</div>
|
|
<div class="stats-link">
|
|
<a href="javascript:;" onclick="filterByFindings('has_findings')">View With Findings <i class="fa fa-arrow-alt-circle-right"></i></a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-xl-3 col-md-6">
|
|
<div class="widget widget-stats bg-red">
|
|
<div class="stats-icon"><i class="fa fa-times-circle"></i></div>
|
|
<div class="stats-info">
|
|
<h4>OVERDUE</h4>
|
|
<p id="overdueAudits">{{ overdue_audits|default:0 }}</p>
|
|
</div>
|
|
<div class="stats-link">
|
|
<a href="javascript:;" onclick="filterByStatus('overdue')">View Overdue <i class="fa fa-arrow-alt-circle-right"></i></a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- END row -->
|
|
|
|
<!-- BEGIN panel -->
|
|
<div class="panel panel-inverse">
|
|
<div class="panel-heading">
|
|
<h4 class="panel-title">Quality Audits Management</h4>
|
|
<div class="panel-heading-btn">
|
|
<button type="button" class="btn btn-primary btn-sm me-2" data-bs-toggle="modal" data-bs-target="#createAuditModal">
|
|
<i class="fa fa-plus me-1"></i>New Audit
|
|
</button>
|
|
<button type="button" class="btn btn-success btn-sm me-2" onclick="scheduleAudits()">
|
|
<i class="fa fa-calendar me-1"></i>Schedule Audits
|
|
</button>
|
|
<div class="btn-group">
|
|
<button type="button" class="btn btn-secondary btn-sm dropdown-toggle" data-bs-toggle="dropdown">
|
|
<i class="fa fa-download me-1"></i>Export
|
|
</button>
|
|
<ul class="dropdown-menu">
|
|
<li><a class="dropdown-item" href="javascript:;" onclick="exportData('csv')">
|
|
<i class="fa fa-file-csv me-2"></i>Export as CSV
|
|
</a></li>
|
|
<li><a class="dropdown-item" href="javascript:;" onclick="exportData('excel')">
|
|
<i class="fa fa-file-excel me-2"></i>Export as Excel
|
|
</a></li>
|
|
<li><a class="dropdown-item" href="javascript:;" onclick="exportData('pdf')">
|
|
<i class="fa fa-file-pdf me-2"></i>Export as PDF
|
|
</a></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="panel-body">
|
|
<!-- Filters -->
|
|
<div class="row mb-3">
|
|
<div class="col-md-3">
|
|
<div class="form-floating">
|
|
<input type="text" class="form-control" id="searchInput" placeholder="Search audits...">
|
|
<label for="searchInput">Search audits...</label>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<div class="form-floating">
|
|
<select class="form-select" id="statusFilter">
|
|
<option value="">All Statuses</option>
|
|
<option value="planned">Planned</option>
|
|
<option value="in_progress">In Progress</option>
|
|
<option value="completed">Completed</option>
|
|
<option value="cancelled">Cancelled</option>
|
|
<option value="overdue">Overdue</option>
|
|
</select>
|
|
<label for="statusFilter">Status</label>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<div class="form-floating">
|
|
<select class="form-select" id="typeFilter">
|
|
<option value="">All Types</option>
|
|
<option value="internal">Internal</option>
|
|
<option value="external">External</option>
|
|
<option value="regulatory">Regulatory</option>
|
|
<option value="accreditation">Accreditation</option>
|
|
</select>
|
|
<label for="typeFilter">Type</label>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<div class="form-floating">
|
|
<select class="form-select" id="departmentFilter">
|
|
<option value="">All Departments</option>
|
|
{% for department in departments %}
|
|
<option value="{{ department.id }}">{{ department.name }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
<label for="departmentFilter">Department</label>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<div class="form-floating">
|
|
<input type="date" class="form-control" id="dateFilter">
|
|
<label for="dateFilter">Audit Date</label>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-1">
|
|
<button type="button" class="btn btn-outline-secondary h-100" onclick="clearFilters()">
|
|
<i class="fa fa-times"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Table -->
|
|
<div class="table-responsive">
|
|
<table class="table table-striped table-hover" id="auditsTable">
|
|
<thead>
|
|
<tr>
|
|
<th width="30">
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" id="selectAll">
|
|
</div>
|
|
</th>
|
|
<th>Audit</th>
|
|
<th>Type</th>
|
|
<th>Department</th>
|
|
<th>Auditor</th>
|
|
<th>Date</th>
|
|
<th>Status</th>
|
|
<th>Findings</th>
|
|
<th width="120">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="auditsTableBody">
|
|
{% for audit in audits %}
|
|
<tr data-audit-id="{{ audit.id }}">
|
|
<td>
|
|
<div class="form-check">
|
|
<input class="form-check-input audit-checkbox" type="checkbox" value="{{ audit.id }}">
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div>
|
|
<strong>{{ audit.title }}</strong>
|
|
<br><small class="text-muted">{{ audit.description|truncatechars:60 }}</small>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
{% if audit.audit_type == 'internal' %}
|
|
<span class="badge bg-primary">Internal</span>
|
|
{% elif audit.audit_type == 'external' %}
|
|
<span class="badge bg-info">External</span>
|
|
{% elif audit.audit_type == 'regulatory' %}
|
|
<span class="badge bg-warning text-dark">Regulatory</span>
|
|
{% elif audit.audit_type == 'accreditation' %}
|
|
<span class="badge bg-success">Accreditation</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>{{ audit.department.name|default:"All Departments" }}</td>
|
|
<td>
|
|
{% if audit.lead_auditor %}
|
|
{{ audit.lead_auditor.get_full_name }}
|
|
{% else %}
|
|
<span class="text-muted">Not assigned</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
{% if audit.scheduled_date %}
|
|
{{ audit.scheduled_date|date:"M d, Y" }}
|
|
{% if audit.is_overdue %}
|
|
<br><small class="text-danger">Overdue</small>
|
|
{% endif %}
|
|
{% else %}
|
|
<span class="text-muted">Not scheduled</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
{% if audit.status == 'planned' %}
|
|
<span class="badge bg-secondary">Planned</span>
|
|
{% elif audit.status == 'in_progress' %}
|
|
<span class="badge bg-info">In Progress</span>
|
|
{% elif audit.status == 'completed' %}
|
|
<span class="badge bg-success">Completed</span>
|
|
{% elif audit.status == 'cancelled' %}
|
|
<span class="badge bg-danger">Cancelled</span>
|
|
{% elif audit.is_overdue %}
|
|
<span class="badge bg-warning text-dark">Overdue</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
{% if audit.findings_count > 0 %}
|
|
<span class="badge bg-orange">{{ audit.findings_count }} finding{{ audit.findings_count|pluralize }}</span>
|
|
{% else %}
|
|
<span class="badge bg-success">No findings</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<div class="btn-group">
|
|
<a href="{% url 'quality:audit_detail' audit.pk %}" class="btn btn-outline-primary btn-sm" title="View Details">
|
|
<i class="fa fa-eye"></i>
|
|
</a>
|
|
<a href="{% url 'quality:audit_edit' audit.pk %}" class="btn btn-outline-secondary btn-sm" title="Edit">
|
|
<i class="fa fa-edit"></i>
|
|
</a>
|
|
{% if audit.status == 'planned' %}
|
|
<button type="button" class="btn btn-outline-success btn-sm" onclick="startAudit('{{ audit.id }}')" title="Start Audit">
|
|
<i class="fa fa-play"></i>
|
|
</button>
|
|
{% elif audit.status == 'in_progress' %}
|
|
<button type="button" class="btn btn-outline-warning btn-sm" onclick="completeAudit('{{ audit.id }}')" title="Complete Audit">
|
|
<i class="fa fa-check"></i>
|
|
</button>
|
|
{% endif %}
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% empty %}
|
|
<tr>
|
|
<td colspan="9" class="text-center text-muted py-4">
|
|
<i class="fa fa-clipboard-check fa-3x mb-3"></i>
|
|
<br>No audits found
|
|
<br><small>Create your first audit to get started</small>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
{% if is_paginated %}
|
|
<nav aria-label="Audits pagination">
|
|
<ul class="pagination justify-content-center">
|
|
{% if page_obj.has_previous %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page=1">« First</a>
|
|
</li>
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page={{ page_obj.previous_page_number }}">Previous</a>
|
|
</li>
|
|
{% endif %}
|
|
|
|
{% for num in page_obj.paginator.page_range %}
|
|
{% if page_obj.number == num %}
|
|
<li class="page-item active">
|
|
<span class="page-link">{{ num }}</span>
|
|
</li>
|
|
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page={{ num }}">{{ num }}</a>
|
|
</li>
|
|
{% endif %}
|
|
{% endfor %}
|
|
|
|
{% if page_obj.has_next %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page={{ page_obj.next_page_number }}">Next</a>
|
|
</li>
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}">Last »</a>
|
|
</li>
|
|
{% endif %}
|
|
</ul>
|
|
</nav>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<!-- END panel -->
|
|
|
|
<!-- Create Audit Modal -->
|
|
<div class="modal fade" id="createAuditModal" tabindex="-1">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">
|
|
<i class="fa fa-plus me-2"></i>Create Quality Audit
|
|
</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="createAuditForm">
|
|
{% csrf_token %}
|
|
<div class="row">
|
|
<div class="col-md-8">
|
|
<div class="form-floating mb-3">
|
|
<input type="text" class="form-control" id="auditTitle" required>
|
|
<label for="auditTitle">Audit Title *</label>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="form-floating mb-3">
|
|
<select class="form-select" id="auditType" required>
|
|
<option value="">Select Type</option>
|
|
<option value="internal">Internal</option>
|
|
<option value="external">External</option>
|
|
<option value="regulatory">Regulatory</option>
|
|
<option value="accreditation">Accreditation</option>
|
|
</select>
|
|
<label for="auditType">Type *</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-floating mb-3">
|
|
<textarea class="form-control" id="auditDescription" style="height: 100px;"></textarea>
|
|
<label for="auditDescription">Description</label>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="form-floating mb-3">
|
|
<select class="form-select" id="auditDepartment">
|
|
<option value="">All Departments</option>
|
|
{% for department in departments %}
|
|
<option value="{{ department.id }}">{{ department.name }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
<label for="auditDepartment">Department</label>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="form-floating mb-3">
|
|
<input type="date" class="form-control" id="scheduledDate">
|
|
<label for="scheduledDate">Scheduled Date</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
<button type="button" class="btn btn-primary" onclick="createAudit()">
|
|
<i class="fa fa-plus me-2"></i>Create Audit
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block js %}
|
|
<script>
|
|
$(document).ready(function() {
|
|
// Select all functionality
|
|
$('#selectAll').change(function() {
|
|
$('.audit-checkbox').prop('checked', $(this).is(':checked'));
|
|
});
|
|
|
|
// Search functionality
|
|
$('#searchInput').on('input', function() {
|
|
filterAudits();
|
|
});
|
|
|
|
// Filter functionality
|
|
$('#statusFilter, #typeFilter, #departmentFilter, #dateFilter').change(function() {
|
|
filterAudits();
|
|
});
|
|
});
|
|
|
|
function filterAudits() {
|
|
const search = $('#searchInput').val().toLowerCase();
|
|
const status = $('#statusFilter').val();
|
|
const type = $('#typeFilter').val();
|
|
const department = $('#departmentFilter').val();
|
|
const date = $('#dateFilter').val();
|
|
|
|
// Build filter parameters
|
|
const params = new URLSearchParams();
|
|
if (search) params.append('search', search);
|
|
if (status) params.append('status', status);
|
|
if (type) params.append('type', type);
|
|
if (department) params.append('department', department);
|
|
if (date) params.append('date', date);
|
|
|
|
// Update URL and reload content
|
|
const newUrl = window.location.pathname + (params.toString() ? '?' + params.toString() : '');
|
|
history.pushState(null, '', newUrl);
|
|
|
|
// Reload content via AJAX
|
|
$.get(newUrl, function(data) {
|
|
const newContent = $(data).find('#auditsTableBody').html();
|
|
$('#auditsTableBody').html(newContent);
|
|
});
|
|
}
|
|
|
|
function clearFilters() {
|
|
$('#searchInput').val('');
|
|
$('#statusFilter').val('');
|
|
$('#typeFilter').val('');
|
|
$('#departmentFilter').val('');
|
|
$('#dateFilter').val('');
|
|
filterAudits();
|
|
}
|
|
|
|
function filterByStatus(status) {
|
|
$('#statusFilter').val(status === 'all' ? '' : status);
|
|
filterAudits();
|
|
}
|
|
|
|
function filterByFindings(hasFindings) {
|
|
// Custom filter for audits with findings
|
|
// Implementation would depend on backend support
|
|
filterAudits();
|
|
}
|
|
|
|
function startAudit(auditId) {
|
|
if (confirm('Start this audit? This will change the status to "In Progress".')) {
|
|
$.ajax({
|
|
url: '{% url "quality:start_audit" %}',
|
|
method: 'POST',
|
|
data: {
|
|
'audit_id': auditId,
|
|
'csrfmiddlewaretoken': '{{ csrf_token }}'
|
|
},
|
|
success: function(response) {
|
|
if (response.success) {
|
|
toastr.success('Audit started successfully');
|
|
location.reload();
|
|
} else {
|
|
toastr.error(response.message || 'Failed to start audit');
|
|
}
|
|
},
|
|
error: function() {
|
|
toastr.error('Failed to start audit');
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function completeAudit(auditId) {
|
|
if (confirm('Complete this audit? This will finalize the audit and generate the report.')) {
|
|
$.ajax({
|
|
url: '{% url "quality:complete_audit" %}',
|
|
method: 'POST',
|
|
data: {
|
|
'audit_id': auditId,
|
|
'csrfmiddlewaretoken': '{{ csrf_token }}'
|
|
},
|
|
success: function(response) {
|
|
if (response.success) {
|
|
toastr.success('Audit completed successfully');
|
|
location.reload();
|
|
} else {
|
|
toastr.error(response.message || 'Failed to complete audit');
|
|
}
|
|
},
|
|
error: function() {
|
|
toastr.error('Failed to complete audit');
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function scheduleAudits() {
|
|
// Open audit scheduling modal or redirect to scheduling page
|
|
window.location.href = '{% url "quality:schedule_audits" %}';
|
|
}
|
|
|
|
function exportData(format) {
|
|
let url = '{% url "quality:export_audits" %}?format=' + format;
|
|
|
|
// Add current filters to export
|
|
const search = $('#searchInput').val();
|
|
const status = $('#statusFilter').val();
|
|
const type = $('#typeFilter').val();
|
|
const department = $('#departmentFilter').val();
|
|
const date = $('#dateFilter').val();
|
|
|
|
if (search) url += '&search=' + encodeURIComponent(search);
|
|
if (status) url += '&status=' + status;
|
|
if (type) url += '&type=' + type;
|
|
if (department) url += '&department=' + department;
|
|
if (date) url += '&date=' + date;
|
|
|
|
window.open(url, '_blank');
|
|
toastr.info('Export started');
|
|
}
|
|
|
|
function createAudit() {
|
|
const formData = {
|
|
'title': $('#auditTitle').val(),
|
|
'type': $('#auditType').val(),
|
|
'description': $('#auditDescription').val(),
|
|
'department': $('#auditDepartment').val(),
|
|
'scheduled_date': $('#scheduledDate').val(),
|
|
'csrfmiddlewaretoken': '{{ csrf_token }}'
|
|
};
|
|
|
|
$.ajax({
|
|
url: '{% url "quality:create_audit" %}',
|
|
method: 'POST',
|
|
data: formData,
|
|
success: function(response) {
|
|
if (response.success) {
|
|
toastr.success('Quality audit created successfully');
|
|
$('#createAuditModal').modal('hide');
|
|
location.reload();
|
|
} else {
|
|
toastr.error(response.message || 'Failed to create audit');
|
|
}
|
|
},
|
|
error: function() {
|
|
toastr.error('Failed to create audit');
|
|
}
|
|
});
|
|
}
|
|
</script>
|
|
{% endblock %}
|
|
|