Marwan Alwali 23158e9fbf update
2025-09-08 03:00:23 +03:00

744 lines
32 KiB
HTML

{% extends 'base.html' %}
{% load static %}
{% block title %}Issue Blood Unit - Request #{{ blood_request.request_number }}{% endblock %}
{% block css %}
<link href="{% static 'plugins/select2/dist/css/select2.min.css' %}" rel="stylesheet" />
<style>
.form-section {
background: #f8f9fa;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
border-left: 4px solid #007bff;
}
.form-section h5 {
color: #007bff;
margin-bottom: 15px;
}
.required-field {
color: #dc3545;
}
.request-info {
background: #d4edda;
border-left: 4px solid #28a745;
}
.unit-selection {
background: #d1ecf1;
border-left: 4px solid #17a2b8;
}
.safety-checks {
background: #fff3cd;
border-left: 4px solid #ffc107;
}
.emergency-section {
background: #f8d7da;
border-left: 4px solid #dc3545;
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.8; }
100% { opacity: 1; }
}
.unit-card {
background: white;
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 15px;
margin-bottom: 15px;
cursor: pointer;
transition: all 0.3s ease;
}
.unit-card:hover {
border-color: #007bff;
box-shadow: 0 2px 8px rgba(0,123,255,0.2);
}
.unit-card.selected {
border-color: #28a745;
background-color: #d4edda;
}
.unit-card.incompatible {
border-color: #dc3545;
background-color: #f8d7da;
cursor: not-allowed;
}
.compatibility-badge {
position: absolute;
top: 10px;
right: 10px;
}
.issue-preview {
background: #e2e3e5;
border-radius: 8px;
padding: 15px;
margin-top: 15px;
}
.verification-checklist {
background: #f8f9fa;
border: 2px solid #28a745;
border-radius: 8px;
padding: 15px;
margin-top: 15px;
}
.transport-info {
background: #e9ecef;
border-radius: 8px;
padding: 15px;
margin-top: 15px;
}
</style>
{% endblock %}
{% block content %}
<!-- BEGIN breadcrumb -->
<ol class="breadcrumb float-xl-end">
<li class="breadcrumb-item"><a href="{% url 'core:dashboard' %}">Home</a></li>
<li class="breadcrumb-item"><a href="{% url 'blood_bank:dashboard' %}">Blood Bank</a></li>
<li class="breadcrumb-item"><a href="{% url 'blood_bank:blood_request_list' %}">Blood Requests</a></li>
<li class="breadcrumb-item"><a href="{% url 'blood_bank:blood_request_detail' blood_request.id %}">{{ blood_request.request_number }}</a></li>
<li class="breadcrumb-item active">Issue Blood</li>
</ol>
<!-- END breadcrumb -->
<!-- BEGIN page-header -->
<h1 class="page-header">
Issue Blood Unit
<small>Request #{{ blood_request.request_number }}</small>
{% if blood_request.urgency == 'emergency' %}
<span class="badge bg-danger ms-2">EMERGENCY</span>
{% endif %}
</h1>
<!-- END page-header -->
<!-- BEGIN panel -->
<div class="panel panel-inverse">
<div class="panel-heading">
<h4 class="panel-title">
<i class="fa fa-shipping-fast"></i> Blood Unit Issue
</h4>
<div class="panel-heading-btn">
<span class="badge bg-info">Request: {{ blood_request.request_number }}</span>
</div>
</div>
<div class="panel-body">
<form method="post" id="bloodIssueForm">
{% csrf_token %}
<!-- BEGIN request information -->
<div class="form-section {% if blood_request.urgency == 'emergency' %}emergency-section{% else %}request-info{% endif %}">
<h5><i class="fa fa-file-medical"></i> Blood Request Information</h5>
<div class="row">
<div class="col-md-6">
<table class="table table-borderless mb-0">
<tr>
<td class="fw-bold">Request Number:</td>
<td>{{ blood_request.request_number }}</td>
</tr>
<tr>
<td class="fw-bold">Patient:</td>
<td>{{ blood_request.patient.full_name }} ({{ blood_request.patient.patient_id }})</td>
</tr>
<tr>
<td class="fw-bold">Blood Group:</td>
<td><span class="badge bg-primary">{{ blood_request.patient.blood_group.display_name }}</span></td>
</tr>
<tr>
<td class="fw-bold">Component Requested:</td>
<td>{{ blood_request.component.get_name_display }}</td>
</tr>
</table>
</div>
<div class="col-md-6">
<table class="table table-borderless mb-0">
<tr>
<td class="fw-bold">Quantity:</td>
<td>{{ blood_request.quantity_requested }} units</td>
</tr>
<tr>
<td class="fw-bold">Urgency:</td>
<td>
<span class="badge bg-{% if blood_request.urgency == 'emergency' %}danger{% elif blood_request.urgency == 'urgent' %}warning{% else %}info{% endif %}">
{{ blood_request.get_urgency_display }}
</span>
</td>
</tr>
<tr>
<td class="fw-bold">Requested By:</td>
<td>{{ blood_request.requested_by.get_full_name }}</td>
</tr>
<tr>
<td class="fw-bold">Department:</td>
<td>{{ blood_request.department.name }}</td>
</tr>
</table>
</div>
</div>
{% if blood_request.special_requirements %}
<div class="mt-3">
<h6>Special Requirements:</h6>
<div class="alert alert-info">{{ blood_request.special_requirements }}</div>
</div>
{% endif %}
</div>
<!-- END request information -->
<!-- BEGIN unit selection -->
<div class="form-section unit-selection">
<h5><i class="fa fa-tint"></i> Available Blood Units</h5>
<div class="row">
<div class="col-md-12">
<p class="text-muted">Select compatible blood units for this request. Units are filtered by blood group compatibility and availability.</p>
<div id="availableUnits">
{% for unit in available_units %}
<div class="unit-card position-relative" data-unit-id="{{ unit.id }}" onclick="selectUnit(this)">
<div class="row">
<div class="col-md-8">
<h6>{{ unit.unit_number }}</h6>
<div class="row">
<div class="col-md-6">
<small class="text-muted">Blood Group:</small><br>
<span class="badge bg-primary">{{ unit.blood_group.display_name }}</span>
</div>
<div class="col-md-6">
<small class="text-muted">Component:</small><br>
{{ unit.component.get_name_display }}
</div>
</div>
<div class="row mt-2">
<div class="col-md-6">
<small class="text-muted">Volume:</small><br>
{{ unit.volume_ml }} ml
</div>
<div class="col-md-6">
<small class="text-muted">Expiry:</small><br>
{{ unit.expiry_date|date:"M d, Y" }}
{% if unit.days_to_expiry <= 3 %}
<span class="badge bg-warning">Expires Soon</span>
{% endif %}
</div>
</div>
</div>
<div class="col-md-4">
<div class="text-end">
<small class="text-muted">Donor:</small><br>
{{ unit.donor.full_name }}<br>
<small class="text-muted">Collection:</small><br>
{{ unit.collection_date|date:"M d, Y" }}<br>
<small class="text-muted">Location:</small><br>
{{ unit.location.name }}
</div>
</div>
</div>
<!-- Compatibility badge -->
<div class="compatibility-badge">
{% if unit.is_compatible %}
<span class="badge bg-success">Compatible</span>
{% else %}
<span class="badge bg-danger">Check Required</span>
{% endif %}
</div>
<!-- Crossmatch status -->
{% if unit.crossmatch_status %}
<div class="mt-2">
<small class="text-muted">Crossmatch:</small>
<span class="badge bg-{% if unit.crossmatch_status == 'compatible' %}success{% elif unit.crossmatch_status == 'incompatible' %}danger{% else %}warning{% endif %}">
{{ unit.crossmatch_status|title }}
</span>
</div>
{% endif %}
</div>
{% empty %}
<div class="alert alert-warning">
<i class="fa fa-exclamation-triangle"></i> No compatible blood units available for this request.
<br>Please check inventory or contact blood bank supervisor.
</div>
{% endfor %}
</div>
<input type="hidden" name="selected_units" id="selectedUnits" value="">
</div>
</div>
</div>
<!-- END unit selection -->
<!-- BEGIN issue details -->
<div class="form-section">
<h5><i class="fa fa-info-circle"></i> Issue Details</h5>
<div class="row">
<div class="col-md-4">
<div class="form-group">
<label for="issueDate" class="form-label">
Issue Date & Time <span class="required-field">*</span>
</label>
<input type="datetime-local" class="form-control" id="issueDate" name="issue_date" required>
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label for="issuedBy" class="form-label">
Issued By <span class="required-field">*</span>
</label>
<select class="form-select" id="issuedBy" name="issued_by" required>
<option value="">Select staff member...</option>
{% for staff in blood_bank_staff %}
<option value="{{ staff.id }}">{{ staff.get_full_name }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label for="receivedBy" class="form-label">
Received By <span class="required-field">*</span>
</label>
<select class="form-select" id="receivedBy" name="received_by" required>
<option value="">Select receiving staff...</option>
{% for staff in clinical_staff %}
<option value="{{ staff.id }}">{{ staff.get_full_name }}</option>
{% endfor %}
</select>
</div>
</div>
</div>
</div>
<!-- END issue details -->
<!-- BEGIN transport information -->
<div class="form-section">
<h5><i class="fa fa-truck"></i> Transport Information</h5>
<div class="transport-info">
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="transportMethod" class="form-label">
Transport Method <span class="required-field">*</span>
</label>
<select class="form-select" id="transportMethod" name="transport_method" required>
<option value="">Select method...</option>
<option value="hand_carry">Hand Carry</option>
<option value="pneumatic_tube">Pneumatic Tube</option>
<option value="courier">Courier Service</option>
<option value="emergency_transport">Emergency Transport</option>
</select>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="transportContainer" class="form-label">
Transport Container <span class="required-field">*</span>
</label>
<select class="form-select" id="transportContainer" name="transport_container" required>
<option value="">Select container...</option>
<option value="insulated_bag">Insulated Transport Bag</option>
<option value="cooler_box">Cooler Box</option>
<option value="temperature_controlled">Temperature Controlled Container</option>
<option value="emergency_kit">Emergency Transport Kit</option>
</select>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="transportTemperature" class="form-label">
Transport Temperature (°C)
</label>
<input type="number" step="0.1" class="form-control" id="transportTemperature"
name="transport_temperature" placeholder="2-6°C" min="1" max="10">
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="estimatedDelivery" class="form-label">
Estimated Delivery Time
</label>
<input type="datetime-local" class="form-control" id="estimatedDelivery" name="estimated_delivery">
</div>
</div>
</div>
</div>
</div>
<!-- END transport information -->
<!-- BEGIN verification checklist -->
<div class="form-section safety-checks">
<h5><i class="fa fa-shield-alt"></i> Pre-Issue Verification Checklist</h5>
<div class="verification-checklist">
<div class="row">
<div class="col-md-6">
<h6>Unit Verification</h6>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="unitNumberVerified" required>
<label class="form-check-label" for="unitNumberVerified">
Blood unit number verified against request
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="bloodGroupVerified" required>
<label class="form-check-label" for="bloodGroupVerified">
Blood group compatibility verified
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="expiryDateChecked" required>
<label class="form-check-label" for="expiryDateChecked">
Expiry date checked and valid
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="visualInspection" required>
<label class="form-check-label" for="visualInspection">
Visual inspection completed (no clots, discoloration)
</label>
</div>
</div>
<div class="col-md-6">
<h6>Documentation & Testing</h6>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="crossmatchCompleted" required>
<label class="form-check-label" for="crossmatchCompleted">
Crossmatch completed and compatible
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="testingCompleted" required>
<label class="form-check-label" for="testingCompleted">
All required testing completed and negative
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="patientIdentityVerified" required>
<label class="form-check-label" for="patientIdentityVerified">
Patient identity verified (2 identifiers)
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="requestAuthorized" required>
<label class="form-check-label" for="requestAuthorized">
Request properly authorized by physician
</label>
</div>
</div>
</div>
<div class="row mt-3">
<div class="col-md-12">
<h6>Emergency Procedures (if applicable)</h6>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="emergencyProtocol">
<label class="form-check-label" for="emergencyProtocol">
Emergency release protocol followed (if applicable)
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="emergencyConsent">
<label class="form-check-label" for="emergencyConsent">
Emergency consent documented (if applicable)
</label>
</div>
</div>
</div>
</div>
</div>
<!-- END verification checklist -->
<!-- BEGIN special instructions -->
<div class="form-section">
<h5><i class="fa fa-clipboard-list"></i> Special Instructions & Notes</h5>
<div class="row">
<div class="col-md-12">
<div class="form-group">
<label for="issueNotes" class="form-label">Issue Notes</label>
<textarea class="form-control" id="issueNotes" name="issue_notes" rows="3"
placeholder="Any special handling instructions, observations, or notes..."></textarea>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="handlingInstructions" class="form-label">Handling Instructions</label>
<select class="form-select" id="handlingInstructions" name="handling_instructions">
<option value="">Select if applicable...</option>
<option value="keep_refrigerated">Keep Refrigerated (2-6°C)</option>
<option value="room_temperature">Room Temperature (20-24°C)</option>
<option value="immediate_use">For Immediate Use</option>
<option value="irradiated">Irradiated Product</option>
<option value="cmv_negative">CMV Negative</option>
<option value="leukoreduced">Leukoreduced</option>
</select>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="returnTime" class="form-label">Return Time Limit</label>
<input type="datetime-local" class="form-control" id="returnTime" name="return_time_limit"
placeholder="If not used, return by...">
</div>
</div>
</div>
</div>
<!-- END special instructions -->
<!-- BEGIN issue preview -->
<div class="issue-preview">
<h6><i class="fa fa-eye"></i> Issue Summary</h6>
<div id="issueSummary">
<p class="text-muted">Select blood units and complete the form to preview issue details</p>
</div>
</div>
<!-- END issue preview -->
<!-- BEGIN form actions -->
<div class="d-flex justify-content-between mt-4">
<a href="{% url 'blood_bank:blood_request_detail' blood_request.id %}" class="btn btn-secondary">
<i class="fa fa-arrow-left"></i> Cancel
</a>
<div>
<button type="button" class="btn btn-info" onclick="validateIssue()">
<i class="fa fa-check"></i> Validate Issue
</button>
<button type="submit" class="btn btn-primary" id="submitBtn" disabled>
<i class="fa fa-shipping-fast"></i> Issue Blood Units
</button>
</div>
</div>
<!-- END form actions -->
</form>
</div>
</div>
<!-- END panel -->
{% endblock %}
{% block js %}
<script src="{% static 'plugins/select2/dist/js/select2.min.js' %}"></script>
<script>
var selectedUnits = [];
$(document).ready(function() {
// Initialize Select2
$('#issuedBy, #receivedBy, #transportMethod, #transportContainer, #handlingInstructions').select2({
theme: 'bootstrap-5'
});
// Set default issue date to now
var now = new Date();
$('#issueDate').val(now.toISOString().slice(0, 16));
// Set estimated delivery time (30 minutes from now)
var deliveryTime = new Date(now.getTime() + 30 * 60000);
$('#estimatedDelivery').val(deliveryTime.toISOString().slice(0, 16));
// Set return time limit (4 hours from now for RBC, 6 hours for platelets)
var returnTime = new Date(now.getTime() + 4 * 60 * 60000);
$('#returnTime').val(returnTime.toISOString().slice(0, 16));
// Update displays when form changes
updateIssueSummary();
// Event listeners
$('.verification-checklist input[type="checkbox"]').on('change', function() {
validateVerificationChecklist();
});
$('#transportMethod, #transportContainer').on('change', function() {
updateIssueSummary();
});
// Form validation
$('#bloodIssueForm').on('submit', function(e) {
if (!validateIssue()) {
e.preventDefault();
}
});
});
function selectUnit(unitCard) {
var unitId = $(unitCard).data('unit-id');
if ($(unitCard).hasClass('incompatible')) {
Swal.fire({
icon: 'warning',
title: 'Incompatible Unit',
text: 'This unit may not be compatible. Please verify crossmatch results.',
confirmButtonText: 'OK'
});
return;
}
if ($(unitCard).hasClass('selected')) {
// Deselect unit
$(unitCard).removeClass('selected');
selectedUnits = selectedUnits.filter(id => id !== unitId);
} else {
// Select unit
$(unitCard).addClass('selected');
selectedUnits.push(unitId);
}
$('#selectedUnits').val(selectedUnits.join(','));
updateIssueSummary();
validateIssue();
}
function updateIssueSummary() {
if (selectedUnits.length === 0) {
$('#issueSummary').html('<p class="text-muted">Select blood units and complete the form to preview issue details</p>');
return;
}
var summaryHtml = '<div class="row">';
summaryHtml += '<div class="col-md-6">';
summaryHtml += '<h6>Selected Units (' + selectedUnits.length + ')</h6>';
selectedUnits.forEach(function(unitId) {
var unitCard = $('.unit-card[data-unit-id="' + unitId + '"]');
var unitNumber = unitCard.find('h6').text();
summaryHtml += '<div class="badge bg-success me-1 mb-1">' + unitNumber + '</div>';
});
summaryHtml += '</div>';
summaryHtml += '<div class="col-md-6">';
summaryHtml += '<h6>Issue Details</h6>';
var issuedBy = $('#issuedBy option:selected').text();
var receivedBy = $('#receivedBy option:selected').text();
var transportMethod = $('#transportMethod option:selected').text();
var issueDate = $('#issueDate').val();
if (issuedBy && issuedBy !== 'Select staff member...') {
summaryHtml += '<p><strong>Issued By:</strong> ' + issuedBy + '</p>';
}
if (receivedBy && receivedBy !== 'Select receiving staff...') {
summaryHtml += '<p><strong>Received By:</strong> ' + receivedBy + '</p>';
}
if (transportMethod && transportMethod !== 'Select method...') {
summaryHtml += '<p><strong>Transport:</strong> ' + transportMethod + '</p>';
}
if (issueDate) {
summaryHtml += '<p><strong>Issue Time:</strong> ' + new Date(issueDate).toLocaleString() + '</p>';
}
summaryHtml += '</div>';
summaryHtml += '</div>';
// Add emergency warning if applicable
{% if blood_request.urgency == 'emergency' %}
summaryHtml += '<div class="alert alert-danger mt-2">';
summaryHtml += '<strong>⚠️ EMERGENCY ISSUE:</strong> Expedited processing and transport required.';
summaryHtml += '</div>';
{% endif %}
$('#issueSummary').html(summaryHtml);
}
function validateVerificationChecklist() {
var checkboxes = $('.verification-checklist input[type="checkbox"]:not(#emergencyProtocol):not(#emergencyConsent)');
var checkedCount = checkboxes.filter(':checked').length;
var totalCount = checkboxes.length;
$('#submitBtn').prop('disabled', checkedCount < totalCount || selectedUnits.length === 0);
if (checkedCount === totalCount) {
$('.verification-checklist').removeClass('border-warning').addClass('border-success');
} else {
$('.verification-checklist').removeClass('border-success').addClass('border-warning');
}
}
function validateIssue() {
var errors = [];
// Check selected units
if (selectedUnits.length === 0) {
errors.push('Please select at least one blood unit');
}
// Check required fields
if (!$('#issueDate').val()) {
errors.push('Please enter issue date and time');
}
if (!$('#issuedBy').val()) {
errors.push('Please select who is issuing the blood');
}
if (!$('#receivedBy').val()) {
errors.push('Please select who is receiving the blood');
}
if (!$('#transportMethod').val()) {
errors.push('Please select transport method');
}
if (!$('#transportContainer').val()) {
errors.push('Please select transport container');
}
// Check verification checklist
var requiredCheckboxes = $('.verification-checklist input[type="checkbox"]:not(#emergencyProtocol):not(#emergencyConsent)');
var checkedCount = requiredCheckboxes.filter(':checked').length;
if (checkedCount < requiredCheckboxes.length) {
errors.push('Please complete all verification checklist items');
}
// Check quantity match
var requestedQuantity = {{ blood_request.units_requested }};
if (selectedUnits.length !== requestedQuantity) {
errors.push('Selected units (' + selectedUnits.length + ') do not match requested quantity (' + requestedQuantity + ')');
}
// Enable/disable submit button
$('#submitBtn').prop('disabled', errors.length > 0);
if (errors.length > 0) {
if (errors.length < 8) { // Only show errors if validation was explicitly requested
Swal.fire({
icon: 'error',
title: 'Validation Errors',
html: '<ul class="text-start"><li>' + errors.join('</li><li>') + '</li></ul>',
confirmButtonText: 'OK'
});
}
return false;
}
Swal.fire({
icon: 'success',
title: 'Validation Passed',
text: 'All requirements met. Ready to issue blood units.',
timer: 1500,
showConfirmButton: false
});
return true;
}
</script>
{% endblock %}