581 lines
24 KiB
HTML
581 lines
24 KiB
HTML
{% extends "base.html" %}
|
|
{% load static %}
|
|
|
|
{% block title %}Risk Assessment - {{ object.title }}{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="d-flex align-items-center mb-3">
|
|
<div>
|
|
<ol class="breadcrumb">
|
|
<li class="breadcrumb-item"><a href="{% url 'quality:dashboard' %}">Quality</a></li>
|
|
<li class="breadcrumb-item"><a href="{% url 'quality:risk_assessment_list' %}">Risk Assessments</a></li>
|
|
<li class="breadcrumb-item active">{{ object.title|truncatechars:30 }}</li>
|
|
</ol>
|
|
<h1 class="page-header mb-0">Risk Assessment Details</h1>
|
|
</div>
|
|
<div class="ms-auto">
|
|
<div class="btn-group">
|
|
<a href="{% url 'quality:risk_assessment_form' object.pk %}" class="btn btn-primary">
|
|
<i class="fas fa-edit me-2"></i>Edit Assessment
|
|
</a>
|
|
<button type="button" class="btn btn-outline-secondary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown">
|
|
<span class="visually-hidden">Toggle Dropdown</span>
|
|
</button>
|
|
<ul class="dropdown-menu">
|
|
<li><a class="dropdown-item" href="#" onclick="printAssessment()">
|
|
<i class="fas fa-print text-primary me-2"></i>Print Assessment
|
|
</a></li>
|
|
<li><a class="dropdown-item" href="#" onclick="exportAssessment()">
|
|
<i class="fas fa-download text-info me-2"></i>Export Assessment
|
|
</a></li>
|
|
<li><a class="dropdown-item" href="#" onclick="duplicateAssessment()">
|
|
<i class="fas fa-copy text-secondary me-2"></i>Duplicate Assessment
|
|
</a></li>
|
|
<li><hr class="dropdown-divider"></li>
|
|
<li><a class="dropdown-item" href="{% url 'quality:risk_assessment_form' %}">
|
|
<i class="fas fa-plus text-success me-2"></i>New Assessment
|
|
</a></li>
|
|
<li><a class="dropdown-item text-danger" href="{% url 'quality:risk_assessment_confirm_delete' object.pk %}">
|
|
<i class="fas fa-trash me-2"></i>Delete Assessment
|
|
</a></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-xl-8">
|
|
<!-- Assessment Overview -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h4 class="card-title">
|
|
<i class="fas fa-shield-alt me-2"></i>
|
|
Assessment Overview
|
|
</h4>
|
|
<div class="card-toolbar">
|
|
<span class="badge bg-{% if object.status == 'draft' %}secondary{% elif object.status == 'in_review' %}warning{% elif object.status == 'approved' %}success{% elif object.status == 'implemented' %}info{% else %}danger{% endif %} fs-6">
|
|
{{ object.get_status_display }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<table class="table table-borderless">
|
|
<tr>
|
|
<td class="fw-bold">Title:</td>
|
|
<td>{{ object.title }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold">Assessment ID:</td>
|
|
<td>
|
|
<span class="badge bg-primary">{{ object.assessment_id }}</span>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold">Risk Category:</td>
|
|
<td>{{ object.get_risk_category_display }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold">Department:</td>
|
|
<td>{{ object.department.name|default:"All Departments" }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold">Assessor:</td>
|
|
<td>{{ object.assessor.get_full_name }}</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<table class="table table-borderless">
|
|
<tr>
|
|
<td class="fw-bold">Assessment Date:</td>
|
|
<td>{{ object.assessment_date|date:"F d, Y" }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold">Review Date:</td>
|
|
<td>{{ object.review_date|date:"F d, Y"|default:"Not scheduled" }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold">Priority:</td>
|
|
<td>
|
|
<span class="badge bg-{% if object.priority == 'critical' %}danger{% elif object.priority == 'high' %}warning{% elif object.priority == 'medium' %}info{% else %}secondary{% endif %}">
|
|
{{ object.get_priority_display }}
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold">Overall Risk Score:</td>
|
|
<td>
|
|
<span class="badge bg-{% if object.overall_risk_score >= 15 %}danger{% elif object.overall_risk_score >= 10 %}warning{% elif object.overall_risk_score >= 5 %}info{% else %}success{% endif %} fs-6">
|
|
{{ object.overall_risk_score|default:0 }}
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold">Risk Level:</td>
|
|
<td>
|
|
<span class="badge bg-{% if object.risk_level == 'very_high' %}danger{% elif object.risk_level == 'high' %}warning{% elif object.risk_level == 'medium' %}info{% elif object.risk_level == 'low' %}success{% else %}secondary{% endif %}">
|
|
{{ object.get_risk_level_display }}
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Risk Description -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h4 class="card-title">
|
|
<i class="fas fa-file-alt me-2"></i>
|
|
Risk Description
|
|
</h4>
|
|
</div>
|
|
<div class="card-body">
|
|
{% if object.description %}
|
|
<div class="alert alert-info">
|
|
{{ object.description|linebreaks }}
|
|
</div>
|
|
{% else %}
|
|
<div class="text-center text-muted py-3">
|
|
<i class="fas fa-file-alt fa-2x mb-2"></i>
|
|
<p>No description provided</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Risk Factors -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h4 class="card-title">
|
|
<i class="fas fa-exclamation-triangle me-2"></i>
|
|
Risk Factors
|
|
</h4>
|
|
</div>
|
|
<div class="card-body">
|
|
{% if risk_factors %}
|
|
<div class="row">
|
|
{% for factor in risk_factors %}
|
|
<div class="col-md-6 mb-3">
|
|
<div class="card bg-light">
|
|
<div class="card-body">
|
|
<h6 class="card-title">{{ factor.name }}</h6>
|
|
<div class="mb-2">
|
|
<strong>Likelihood:</strong>
|
|
<span class="badge bg-{% if factor.likelihood >= 4 %}danger{% elif factor.likelihood >= 3 %}warning{% else %}info{% endif %}">
|
|
{{ factor.likelihood }}/5
|
|
</span>
|
|
</div>
|
|
<div class="mb-2">
|
|
<strong>Impact:</strong>
|
|
<span class="badge bg-{% if factor.impact >= 4 %}danger{% elif factor.impact >= 3 %}warning{% else %}info{% endif %}">
|
|
{{ factor.impact }}/5
|
|
</span>
|
|
</div>
|
|
<div>
|
|
<strong>Risk Score:</strong>
|
|
<span class="badge bg-{% if factor.risk_score >= 15 %}danger{% elif factor.risk_score >= 10 %}warning{% elif factor.risk_score >= 5 %}info{% else %}success{% endif %}">
|
|
{{ factor.risk_score }}
|
|
</span>
|
|
</div>
|
|
{% if factor.description %}
|
|
<div class="mt-2">
|
|
<small class="text-muted">{{ factor.description }}</small>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% else %}
|
|
<div class="text-center text-muted py-3">
|
|
<i class="fas fa-exclamation-triangle fa-2x mb-2"></i>
|
|
<p>No risk factors identified</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Mitigation Strategies -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h4 class="card-title">
|
|
<i class="fas fa-shield-alt me-2"></i>
|
|
Mitigation Strategies
|
|
</h4>
|
|
</div>
|
|
<div class="card-body">
|
|
{% if object.mitigation_strategies %}
|
|
<div class="alert alert-success">
|
|
{{ object.mitigation_strategies|linebreaks }}
|
|
</div>
|
|
{% else %}
|
|
<div class="text-center text-muted py-3">
|
|
<i class="fas fa-shield-alt fa-2x mb-2"></i>
|
|
<p>No mitigation strategies defined</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Implementation Plan -->
|
|
{% if object.implementation_plan %}
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h4 class="card-title">
|
|
<i class="fas fa-tasks me-2"></i>
|
|
Implementation Plan
|
|
</h4>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="alert alert-info">
|
|
{{ object.implementation_plan|linebreaks }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Monitoring Plan -->
|
|
{% if object.monitoring_plan %}
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h4 class="card-title">
|
|
<i class="fas fa-chart-line me-2"></i>
|
|
Monitoring Plan
|
|
</h4>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="alert alert-warning">
|
|
{{ object.monitoring_plan|linebreaks }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Assessment History -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h4 class="card-title">
|
|
<i class="fas fa-history me-2"></i>
|
|
Assessment History
|
|
</h4>
|
|
</div>
|
|
<div class="card-body">
|
|
{% if assessment_history %}
|
|
<div class="timeline">
|
|
{% for entry in assessment_history %}
|
|
<div class="timeline-item">
|
|
<div class="timeline-marker bg-{% if entry.action == 'created' %}primary{% elif entry.action == 'updated' %}info{% elif entry.action == 'approved' %}success{% elif entry.action == 'rejected' %}danger{% else %}secondary{% endif %}">
|
|
<i class="fas fa-{% if entry.action == 'created' %}plus{% elif entry.action == 'updated' %}edit{% elif entry.action == 'approved' %}check{% elif entry.action == 'rejected' %}times{% else %}circle{% endif %}"></i>
|
|
</div>
|
|
<div class="timeline-content">
|
|
<div class="d-flex justify-content-between">
|
|
<h6 class="mb-1">{{ entry.action|title }}</h6>
|
|
<small class="text-muted">{{ entry.timestamp|date:"M d, Y g:i A" }}</small>
|
|
</div>
|
|
<p class="mb-1">{{ entry.description }}</p>
|
|
<small class="text-muted">by {{ entry.user.get_full_name }}</small>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% else %}
|
|
<div class="text-center text-muted py-4">
|
|
<i class="fas fa-history fa-3x mb-3"></i>
|
|
<h5>No History Available</h5>
|
|
<p>No assessment history recorded.</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-xl-4">
|
|
<!-- Risk Matrix -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h5 class="card-title">
|
|
<i class="fas fa-th me-2"></i>
|
|
Risk Matrix
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="risk-matrix">
|
|
<table class="table table-bordered text-center">
|
|
<thead>
|
|
<tr>
|
|
<th></th>
|
|
<th colspan="5">Likelihood</th>
|
|
</tr>
|
|
<tr>
|
|
<th>Impact</th>
|
|
<th>1</th>
|
|
<th>2</th>
|
|
<th>3</th>
|
|
<th>4</th>
|
|
<th>5</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for i in "54321" %}
|
|
<tr>
|
|
<th>{{ i }}</th>
|
|
{% for j in "12345" %}
|
|
<td class="risk-cell risk-{{ i|add:j|floatformat:0 }}"
|
|
data-risk="{{ i|mul:j }}">
|
|
{{ i|mul:j }}
|
|
</td>
|
|
{% endfor %}
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
<div class="mt-2">
|
|
<small class="text-muted">
|
|
Current Risk Score: <strong>{{ object.overall_risk_score|default:0 }}</strong>
|
|
</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Quick Actions -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h5 class="card-title">
|
|
<i class="fas fa-bolt me-2"></i>
|
|
Quick Actions
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="d-grid gap-2">
|
|
{% if object.status == 'draft' %}
|
|
<button type="button" class="btn btn-success" onclick="submitForReview()">
|
|
<i class="fas fa-paper-plane me-2"></i>Submit for Review
|
|
</button>
|
|
{% endif %}
|
|
|
|
{% if object.status == 'in_review' and user.has_perm:'quality.approve_risk_assessment' %}
|
|
<button type="button" class="btn btn-success" onclick="approveAssessment()">
|
|
<i class="fas fa-check me-2"></i>Approve Assessment
|
|
</button>
|
|
<button type="button" class="btn btn-warning" onclick="requestChanges()">
|
|
<i class="fas fa-edit me-2"></i>Request Changes
|
|
</button>
|
|
{% endif %}
|
|
|
|
{% if object.status == 'approved' %}
|
|
<button type="button" class="btn btn-info" onclick="markImplemented()">
|
|
<i class="fas fa-tasks me-2"></i>Mark as Implemented
|
|
</button>
|
|
{% endif %}
|
|
|
|
<button type="button" class="btn btn-outline-primary" onclick="scheduleReview()">
|
|
<i class="fas fa-calendar me-2"></i>Schedule Review
|
|
</button>
|
|
<button type="button" class="btn btn-outline-secondary" onclick="addComment()">
|
|
<i class="fas fa-comment me-2"></i>Add Comment
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Related Items -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h5 class="card-title">
|
|
<i class="fas fa-link me-2"></i>
|
|
Related Items
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
{% if related_incidents %}
|
|
<h6>Related Incidents</h6>
|
|
{% for incident in related_incidents %}
|
|
<div class="mb-2">
|
|
<a href="{% url 'quality:incident_detail' incident.pk %}" class="text-decoration-none">
|
|
{{ incident.incident_id }} - {{ incident.title|truncatechars:30 }}
|
|
</a>
|
|
</div>
|
|
{% endfor %}
|
|
{% endif %}
|
|
|
|
{% if related_audits %}
|
|
<h6 class="mt-3">Related Audits</h6>
|
|
{% for audit in related_audits %}
|
|
<div class="mb-2">
|
|
<a href="{% url 'quality:audit_detail' audit.pk %}" class="text-decoration-none">
|
|
{{ audit.title|truncatechars:30 }}
|
|
</a>
|
|
</div>
|
|
{% endfor %}
|
|
{% endif %}
|
|
|
|
{% if not related_incidents and not related_audits %}
|
|
<div class="text-center text-muted py-3">
|
|
<i class="fas fa-link fa-2x mb-2"></i>
|
|
<p class="small">No related items</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- System Information -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title">
|
|
<i class="fas fa-info-circle me-2"></i>
|
|
System Information
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="mb-2">
|
|
<strong>Assessment ID:</strong> {{ object.pk }}
|
|
</div>
|
|
<div class="mb-2">
|
|
<strong>Created:</strong> {{ object.created_at|date:"M d, Y g:i A" }}
|
|
</div>
|
|
<div class="mb-2">
|
|
<strong>Last Updated:</strong> {{ object.updated_at|date:"M d, Y g:i A" }}
|
|
</div>
|
|
<div class="mb-2">
|
|
<strong>Version:</strong> {{ object.version|default:"1.0" }}
|
|
</div>
|
|
{% if object.approved_by %}
|
|
<div class="mb-2">
|
|
<strong>Approved By:</strong> {{ object.approved_by.get_full_name }}
|
|
</div>
|
|
<div>
|
|
<strong>Approval Date:</strong> {{ object.approval_date|date:"M d, Y" }}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
function submitForReview() {
|
|
if (confirm('Submit this risk assessment for review?')) {
|
|
// In a real implementation, this would make an AJAX call
|
|
console.log('Submitting assessment for review');
|
|
location.reload();
|
|
}
|
|
}
|
|
|
|
function approveAssessment() {
|
|
if (confirm('Approve this risk assessment?')) {
|
|
// In a real implementation, this would make an AJAX call
|
|
console.log('Approving assessment');
|
|
location.reload();
|
|
}
|
|
}
|
|
|
|
function requestChanges() {
|
|
const changes = prompt('What changes are needed?');
|
|
if (changes) {
|
|
console.log('Requesting changes:', changes);
|
|
location.reload();
|
|
}
|
|
}
|
|
|
|
function markImplemented() {
|
|
if (confirm('Mark this risk assessment as implemented?')) {
|
|
// In a real implementation, this would make an AJAX call
|
|
console.log('Marking as implemented');
|
|
location.reload();
|
|
}
|
|
}
|
|
|
|
function scheduleReview() {
|
|
const date = prompt('Enter review date (YYYY-MM-DD):');
|
|
if (date) {
|
|
console.log('Scheduling review for:', date);
|
|
location.reload();
|
|
}
|
|
}
|
|
|
|
function addComment() {
|
|
const comment = prompt('Enter your comment:');
|
|
if (comment) {
|
|
console.log('Adding comment:', comment);
|
|
location.reload();
|
|
}
|
|
}
|
|
|
|
function printAssessment() {
|
|
window.print();
|
|
}
|
|
|
|
function exportAssessment() {
|
|
alert('Export assessment functionality would be implemented here.');
|
|
}
|
|
|
|
function duplicateAssessment() {
|
|
if (confirm('Create a copy of this risk assessment?')) {
|
|
window.location.href = "{% url 'quality:risk_assessment_form' %}?duplicate={{ object.pk }}";
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style>
|
|
.timeline {
|
|
position: relative;
|
|
padding-left: 30px;
|
|
}
|
|
|
|
.timeline-item {
|
|
position: relative;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.timeline-item:not(:last-child)::before {
|
|
content: '';
|
|
position: absolute;
|
|
left: -19px;
|
|
top: 30px;
|
|
height: calc(100% + 10px);
|
|
width: 2px;
|
|
background-color: #dee2e6;
|
|
}
|
|
|
|
.timeline-marker {
|
|
position: absolute;
|
|
left: -30px;
|
|
top: 0;
|
|
width: 24px;
|
|
height: 24px;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: white;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.timeline-content {
|
|
background: #f8f9fa;
|
|
padding: 15px;
|
|
border-radius: 8px;
|
|
border-left: 3px solid #dee2e6;
|
|
}
|
|
|
|
.risk-matrix .risk-cell {
|
|
width: 40px;
|
|
height: 40px;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.risk-cell.risk-1, .risk-cell.risk-2 { background-color: #d4edda; color: #155724; }
|
|
.risk-cell.risk-3, .risk-cell.risk-4, .risk-cell.risk-6 { background-color: #fff3cd; color: #856404; }
|
|
.risk-cell.risk-5, .risk-cell.risk-8, .risk-cell.risk-9, .risk-cell.risk-10 { background-color: #f8d7da; color: #721c24; }
|
|
.risk-cell.risk-12, .risk-cell.risk-15, .risk-cell.risk-16, .risk-cell.risk-20, .risk-cell.risk-25 { background-color: #721c24; color: white; }
|
|
</style>
|
|
{% endblock %}
|
|
|