544 lines
25 KiB
HTML
544 lines
25 KiB
HTML
{% extends "base.html" %}
|
|
{% load static %}
|
|
|
|
{% block title %}{% if form.instance.pk %}Edit{% else %}Create{% endif %} Audit - 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"><a href="{% url 'quality:audit_list' %}">Audits</a></li>
|
|
<li class="breadcrumb-item active">{% if form.instance.pk %}Edit{% else %}Create{% endif %} Audit</li>
|
|
</ol>
|
|
<!-- END breadcrumb -->
|
|
|
|
<!-- BEGIN page-header -->
|
|
<h1 class="page-header">
|
|
{% if form.instance.pk %}Edit Audit{% else %}Create Quality Audit{% endif %}
|
|
<small>{% if form.instance.pk %}Update audit information{% else %}Schedule a new quality audit{% endif %}</small>
|
|
</h1>
|
|
<!-- END page-header -->
|
|
|
|
<!-- BEGIN row -->
|
|
<div class="row">
|
|
<div class="col-xl-8">
|
|
<!-- Main Form -->
|
|
<div class="panel panel-inverse">
|
|
<div class="panel-heading">
|
|
<h4 class="panel-title">Audit Information</h4>
|
|
<div class="panel-heading-btn">
|
|
<button type="button" class="btn btn-secondary btn-sm" onclick="saveDraft()">
|
|
<i class="fa fa-save me-1"></i>Save Draft
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="panel-body">
|
|
<form method="post" id="auditForm" novalidate>
|
|
{% csrf_token %}
|
|
|
|
<!-- Basic Information -->
|
|
<div class="row">
|
|
<div class="col-md-8">
|
|
<div class="form-floating mb-3">
|
|
{{ form.title }}
|
|
<label for="{{ form.title.id_for_label }}">Audit Title *</label>
|
|
{% if form.title.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.title.errors.0 }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="form-floating mb-3">
|
|
{{ form.audit_type }}
|
|
<label for="{{ form.audit_type.id_for_label }}">Audit Type *</label>
|
|
{% if form.audit_type.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.audit_type.errors.0 }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-floating mb-3">
|
|
{{ form.description }}
|
|
<label for="{{ form.description.id_for_label }}">Description</label>
|
|
{% if form.description.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.description.errors.0 }}</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="form-floating mb-3">
|
|
{{ form.scope }}
|
|
<label for="{{ form.scope.id_for_label }}">Audit Scope</label>
|
|
{% if form.scope.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.scope.errors.0 }}</div>
|
|
{% endif %}
|
|
<div class="form-text">Define what areas, processes, or departments will be audited</div>
|
|
</div>
|
|
|
|
<!-- Scheduling -->
|
|
<h6 class="mt-4 mb-3">Scheduling Information</h6>
|
|
<div class="row">
|
|
<div class="col-md-4">
|
|
<div class="form-floating mb-3">
|
|
{{ form.scheduled_date }}
|
|
<label for="{{ form.scheduled_date.id_for_label }}">Scheduled Date</label>
|
|
{% if form.scheduled_date.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.scheduled_date.errors.0 }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="form-floating mb-3">
|
|
{{ form.estimated_duration }}
|
|
<label for="{{ form.estimated_duration.id_for_label }}">Estimated Duration (days)</label>
|
|
{% if form.estimated_duration.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.estimated_duration.errors.0 }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="form-floating mb-3">
|
|
{{ form.priority }}
|
|
<label for="{{ form.priority.id_for_label }}">Priority</label>
|
|
{% if form.priority.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.priority.errors.0 }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Department and Team -->
|
|
<h6 class="mt-4 mb-3">Department and Team</h6>
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="form-floating mb-3">
|
|
{{ form.department }}
|
|
<label for="{{ form.department.id_for_label }}">Department</label>
|
|
{% if form.department.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.department.errors.0 }}</div>
|
|
{% endif %}
|
|
<div class="form-text">Leave blank for organization-wide audits</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="form-floating mb-3">
|
|
{{ form.lead_auditor }}
|
|
<label for="{{ form.lead_auditor.id_for_label }}">Lead Auditor</label>
|
|
{% if form.lead_auditor.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.lead_auditor.errors.0 }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Team Members -->
|
|
<div class="mb-3">
|
|
<label class="form-label">Audit Team Members</label>
|
|
<div id="teamMembersContainer">
|
|
{% if form.instance.pk %}
|
|
{% for member in form.instance.team_members.all %}
|
|
<div class="d-flex align-items-center mb-2 team-member-row">
|
|
<select class="form-select me-2" name="team_members">
|
|
<option value="">Select team member</option>
|
|
{% for user in available_users %}
|
|
<option value="{{ user.id }}" {% if user.id == member.id %}selected{% endif %}>
|
|
{{ user.get_full_name }} - {{ user.email }}
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
<button type="button" class="btn btn-outline-danger btn-sm" onclick="removeTeamMember(this)">
|
|
<i class="fa fa-times"></i>
|
|
</button>
|
|
</div>
|
|
{% endfor %}
|
|
{% endif %}
|
|
</div>
|
|
<button type="button" class="btn btn-outline-primary btn-sm" onclick="addTeamMember()">
|
|
<i class="fa fa-plus me-1"></i>Add Team Member
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Standards and Criteria -->
|
|
<h6 class="mt-4 mb-3">Audit Standards and Criteria</h6>
|
|
<div class="form-floating mb-3">
|
|
{{ form.standards }}
|
|
<label for="{{ form.standards.id_for_label }}">Applicable Standards</label>
|
|
{% if form.standards.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.standards.errors.0 }}</div>
|
|
{% endif %}
|
|
<div class="form-text">List relevant standards, regulations, or policies (e.g., ISO 9001, Joint Commission, etc.)</div>
|
|
</div>
|
|
|
|
<div class="form-floating mb-3">
|
|
{{ form.criteria }}
|
|
<label for="{{ form.criteria.id_for_label }}">Audit Criteria</label>
|
|
{% if form.criteria.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.criteria.errors.0 }}</div>
|
|
{% endif %}
|
|
<div class="form-text">Specific criteria or requirements to be evaluated</div>
|
|
</div>
|
|
|
|
<!-- Objectives -->
|
|
<h6 class="mt-4 mb-3">Audit Objectives</h6>
|
|
<div class="form-floating mb-3">
|
|
{{ form.objectives }}
|
|
<label for="{{ form.objectives.id_for_label }}">Objectives</label>
|
|
{% if form.objectives.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.objectives.errors.0 }}</div>
|
|
{% endif %}
|
|
<div class="form-text">What the audit aims to achieve or verify</div>
|
|
</div>
|
|
|
|
<!-- Methodology -->
|
|
<h6 class="mt-4 mb-3">Audit Methodology</h6>
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label class="form-label">Audit Methods</label>
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" name="methods" value="document_review" id="method1">
|
|
<label class="form-check-label" for="method1">Document Review</label>
|
|
</div>
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" name="methods" value="interviews" id="method2">
|
|
<label class="form-check-label" for="method2">Staff Interviews</label>
|
|
</div>
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" name="methods" value="observation" id="method3">
|
|
<label class="form-check-label" for="method3">Direct Observation</label>
|
|
</div>
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" name="methods" value="testing" id="method4">
|
|
<label class="form-check-label" for="method4">Process Testing</label>
|
|
</div>
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" name="methods" value="sampling" id="method5">
|
|
<label class="form-check-label" for="method5">Statistical Sampling</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="form-floating mb-3">
|
|
{{ form.sampling_method }}
|
|
<label for="{{ form.sampling_method.id_for_label }}">Sampling Method</label>
|
|
{% if form.sampling_method.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.sampling_method.errors.0 }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Additional Information -->
|
|
<h6 class="mt-4 mb-3">Additional Information</h6>
|
|
<div class="form-floating mb-3">
|
|
{{ form.special_requirements }}
|
|
<label for="{{ form.special_requirements.id_for_label }}">Special Requirements</label>
|
|
{% if form.special_requirements.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.special_requirements.errors.0 }}</div>
|
|
{% endif %}
|
|
<div class="form-text">Any special access, equipment, or preparation needed</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="form-check mb-3">
|
|
{{ form.is_regulatory }}
|
|
<label class="form-check-label" for="{{ form.is_regulatory.id_for_label }}">
|
|
Regulatory Audit
|
|
</label>
|
|
<div class="form-text">Check if this is a regulatory compliance audit</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="form-check mb-3">
|
|
{{ form.is_external }}
|
|
<label class="form-check-label" for="{{ form.is_external.id_for_label }}">
|
|
External Audit
|
|
</label>
|
|
<div class="form-text">Check if conducted by external auditors</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Form Actions -->
|
|
<div class="d-flex justify-content-between mt-4">
|
|
<div>
|
|
<a href="{% if form.instance.pk %}{% url 'quality:audit_detail' form.instance.pk %}{% else %}{% url 'quality:audit_list' %}{% endif %}" class="btn btn-secondary">
|
|
<i class="fa fa-times me-2"></i>Cancel
|
|
</a>
|
|
</div>
|
|
<div>
|
|
<button type="button" class="btn btn-outline-primary me-2" onclick="saveDraft()">
|
|
<i class="fa fa-save me-2"></i>Save Draft
|
|
</button>
|
|
<button type="submit" class="btn btn-primary">
|
|
<i class="fa fa-check me-2"></i>{% if form.instance.pk %}Update{% else %}Create{% endif %} Audit
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-xl-4">
|
|
<!-- Help and Guidelines -->
|
|
<div class="panel panel-inverse">
|
|
<div class="panel-heading">
|
|
<h4 class="panel-title">Audit Guidelines</h4>
|
|
</div>
|
|
<div class="panel-body">
|
|
<div class="alert alert-info">
|
|
<h6><i class="fa fa-info-circle me-2"></i>Audit Planning Tips</h6>
|
|
<ul class="mb-0 small">
|
|
<li>Define clear objectives and scope</li>
|
|
<li>Select qualified audit team members</li>
|
|
<li>Allow adequate time for preparation</li>
|
|
<li>Communicate with auditees in advance</li>
|
|
<li>Prepare audit checklists and tools</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="alert alert-warning">
|
|
<h6><i class="fa fa-exclamation-triangle me-2"></i>Important Notes</h6>
|
|
<ul class="mb-0 small">
|
|
<li>Regulatory audits require special preparation</li>
|
|
<li>External audits may need additional coordination</li>
|
|
<li>Ensure all team members are available</li>
|
|
<li>Consider operational impact when scheduling</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Audit Checklist -->
|
|
<div class="panel panel-inverse">
|
|
<div class="panel-heading">
|
|
<h4 class="panel-title">Pre-Audit Checklist</h4>
|
|
</div>
|
|
<div class="panel-body">
|
|
<div class="form-check mb-2">
|
|
<input class="form-check-input" type="checkbox" id="check1">
|
|
<label class="form-check-label small" for="check1">
|
|
Audit objectives defined
|
|
</label>
|
|
</div>
|
|
<div class="form-check mb-2">
|
|
<input class="form-check-input" type="checkbox" id="check2">
|
|
<label class="form-check-label small" for="check2">
|
|
Scope clearly documented
|
|
</label>
|
|
</div>
|
|
<div class="form-check mb-2">
|
|
<input class="form-check-input" type="checkbox" id="check3">
|
|
<label class="form-check-label small" for="check3">
|
|
Lead auditor assigned
|
|
</label>
|
|
</div>
|
|
<div class="form-check mb-2">
|
|
<input class="form-check-input" type="checkbox" id="check4">
|
|
<label class="form-check-label small" for="check4">
|
|
Team members selected
|
|
</label>
|
|
</div>
|
|
<div class="form-check mb-2">
|
|
<input class="form-check-input" type="checkbox" id="check5">
|
|
<label class="form-check-label small" for="check5">
|
|
Standards and criteria identified
|
|
</label>
|
|
</div>
|
|
<div class="form-check mb-2">
|
|
<input class="form-check-input" type="checkbox" id="check6">
|
|
<label class="form-check-label small" for="check6">
|
|
Schedule coordinated with department
|
|
</label>
|
|
</div>
|
|
<div class="form-check mb-2">
|
|
<input class="form-check-input" type="checkbox" id="check7">
|
|
<label class="form-check-label small" for="check7">
|
|
Audit tools and checklists prepared
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recent Audits -->
|
|
{% if recent_audits %}
|
|
<div class="panel panel-inverse">
|
|
<div class="panel-heading">
|
|
<h4 class="panel-title">Recent Audits</h4>
|
|
</div>
|
|
<div class="panel-body">
|
|
{% for audit in recent_audits %}
|
|
<div class="d-flex align-items-center mb-2">
|
|
<div class="flex-fill">
|
|
<div class="fw-bold small">{{ audit.title|truncatechars:30 }}</div>
|
|
<div class="text-muted small">{{ audit.scheduled_date|date:"M d, Y" }}</div>
|
|
</div>
|
|
<div>
|
|
{% if audit.status == 'completed' %}
|
|
<span class="badge bg-success">Completed</span>
|
|
{% elif audit.status == 'in_progress' %}
|
|
<span class="badge bg-info">In Progress</span>
|
|
{% else %}
|
|
<span class="badge bg-secondary">{{ audit.get_status_display }}</span>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<!-- END row -->
|
|
{% endblock %}
|
|
|
|
{% block js %}
|
|
<script>
|
|
$(document).ready(function() {
|
|
// Auto-save functionality
|
|
let autoSaveTimer;
|
|
|
|
$('#auditForm input, #auditForm textarea, #auditForm select').on('input change', function() {
|
|
clearTimeout(autoSaveTimer);
|
|
autoSaveTimer = setTimeout(function() {
|
|
saveDraft(true); // Silent save
|
|
}, 30000); // Auto-save after 30 seconds of inactivity
|
|
});
|
|
|
|
// Form validation
|
|
$('#auditForm').on('submit', function(e) {
|
|
if (!validateForm()) {
|
|
e.preventDefault();
|
|
return false;
|
|
}
|
|
});
|
|
|
|
// Load saved methods if editing
|
|
{% if form.instance.pk and form.instance.methods %}
|
|
const savedMethods = '{{ form.instance.methods }}'.split(',');
|
|
savedMethods.forEach(function(method) {
|
|
$('input[name="methods"][value="' + method.trim() + '"]').prop('checked', true);
|
|
});
|
|
{% endif %}
|
|
});
|
|
|
|
function addTeamMember() {
|
|
const container = $('#teamMembersContainer');
|
|
const newRow = $(`
|
|
<div class="d-flex align-items-center mb-2 team-member-row">
|
|
<select class="form-select me-2" name="team_members">
|
|
<option value="">Select team member</option>
|
|
{% for user in available_users %}
|
|
<option value="{{ user.id }}">{{ user.get_full_name }} - {{ user.email }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
<button type="button" class="btn btn-outline-danger btn-sm" onclick="removeTeamMember(this)">
|
|
<i class="fa fa-times"></i>
|
|
</button>
|
|
</div>
|
|
`);
|
|
container.append(newRow);
|
|
}
|
|
|
|
function removeTeamMember(button) {
|
|
$(button).closest('.team-member-row').remove();
|
|
}
|
|
|
|
function validateForm() {
|
|
let isValid = true;
|
|
|
|
// Clear previous errors
|
|
$('.is-invalid').removeClass('is-invalid');
|
|
|
|
// Required field validation
|
|
const requiredFields = ['{{ form.title.id_for_label }}', '{{ form.audit_type.id_for_label }}'];
|
|
|
|
requiredFields.forEach(function(fieldId) {
|
|
const field = $('#' + fieldId);
|
|
if (!field.val().trim()) {
|
|
field.addClass('is-invalid');
|
|
isValid = false;
|
|
}
|
|
});
|
|
|
|
// Date validation
|
|
const scheduledDate = $('#{{ form.scheduled_date.id_for_label }}').val();
|
|
if (scheduledDate && new Date(scheduledDate) < new Date()) {
|
|
$('#{{ form.scheduled_date.id_for_label }}').addClass('is-invalid');
|
|
toastr.error('Scheduled date cannot be in the past');
|
|
isValid = false;
|
|
}
|
|
|
|
if (!isValid) {
|
|
toastr.error('Please correct the highlighted fields');
|
|
}
|
|
|
|
return isValid;
|
|
}
|
|
|
|
function saveDraft(silent = false) {
|
|
const formData = new FormData($('#auditForm')[0]);
|
|
|
|
// Add selected methods
|
|
const methods = [];
|
|
$('input[name="methods"]:checked').each(function() {
|
|
methods.push($(this).val());
|
|
});
|
|
formData.append('methods', methods.join(','));
|
|
|
|
// Add team members
|
|
const teamMembers = [];
|
|
$('select[name="team_members"]').each(function() {
|
|
if ($(this).val()) {
|
|
teamMembers.push($(this).val());
|
|
}
|
|
});
|
|
formData.append('team_members_list', JSON.stringify(teamMembers));
|
|
|
|
formData.append('save_draft', 'true');
|
|
|
|
$.ajax({
|
|
url: window.location.href,
|
|
method: 'POST',
|
|
data: formData,
|
|
processData: false,
|
|
contentType: false,
|
|
success: function(response) {
|
|
if (!silent) {
|
|
toastr.success('Draft saved successfully');
|
|
}
|
|
},
|
|
error: function() {
|
|
if (!silent) {
|
|
toastr.error('Failed to save draft');
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Update checklist based on form completion
|
|
function updateChecklist() {
|
|
$('#check1').prop('checked', $('#{{ form.objectives.id_for_label }}').val().trim() !== '');
|
|
$('#check2').prop('checked', $('#{{ form.scope.id_for_label }}').val().trim() !== '');
|
|
$('#check3').prop('checked', $('#{{ form.lead_auditor.id_for_label }}').val() !== '');
|
|
$('#check4').prop('checked', $('select[name="team_members"]').length > 0);
|
|
$('#check5').prop('checked', $('#{{ form.standards.id_for_label }}').val().trim() !== '');
|
|
$('#check6').prop('checked', $('#{{ form.scheduled_date.id_for_label }}').val() !== '');
|
|
}
|
|
|
|
// Update checklist when form changes
|
|
$('#auditForm input, #auditForm textarea, #auditForm select').on('input change', function() {
|
|
setTimeout(updateChecklist, 100);
|
|
});
|
|
|
|
// Initial checklist update
|
|
$(document).ready(function() {
|
|
updateChecklist();
|
|
});
|
|
</script>
|
|
{% endblock %}
|
|
|