506 lines
24 KiB
HTML
506 lines
24 KiB
HTML
<!-- Transfer Stock Modal -->
|
|
<div class="modal fade" id="transferStockModal" tabindex="-1" aria-labelledby="transferStockModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="transferStockModalLabel">
|
|
<i class="fas fa-exchange-alt me-2"></i>Transfer Stock
|
|
</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<form id="transferStockForm" method="post">
|
|
{% csrf_token %}
|
|
<div class="modal-body">
|
|
<!-- Item Information -->
|
|
<div class="row mb-4">
|
|
<div class="col-12">
|
|
<div class="card bg-light">
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-8">
|
|
<h6 class="card-title mb-1" id="transfer-item-name">Item Name</h6>
|
|
<p class="card-text small text-muted mb-1">
|
|
<span class="me-3">
|
|
<i class="fas fa-barcode me-1"></i>
|
|
<span id="transfer-item-code">Item Code</span>
|
|
</span>
|
|
<span class="me-3">
|
|
<i class="fas fa-layer-group me-1"></i>
|
|
<span id="transfer-item-category">Category</span>
|
|
</span>
|
|
</p>
|
|
<p class="card-text small text-muted mb-0">
|
|
<i class="fas fa-map-marker-alt me-1"></i>
|
|
<strong>From:</strong> <span id="transfer-from-location">Location</span>
|
|
</p>
|
|
</div>
|
|
<div class="col-md-4 text-end">
|
|
<div class="current-stock">
|
|
<small class="text-muted">Available Stock</small>
|
|
<h4 class="mb-0" id="transfer-available-stock">0</h4>
|
|
<small class="text-muted" id="transfer-stock-unit">units</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Transfer Details -->
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="form-group mb-3">
|
|
<label class="form-label" for="transfer_to_location">
|
|
Transfer To <span class="text-danger">*</span>
|
|
</label>
|
|
<select class="form-select" id="transfer_to_location" name="transfer_to_location" required>
|
|
<option value="">Select destination...</option>
|
|
{% for location in pharmacy_locations %}
|
|
<option value="{{ location.id }}" data-location-name="{{ location.name }}">
|
|
{{ location.name }} - {{ location.location_type }}
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="form-group mb-3">
|
|
<label class="form-label" for="transfer_quantity">
|
|
Quantity to Transfer <span class="text-danger">*</span>
|
|
</label>
|
|
<div class="input-group">
|
|
<input type="number" class="form-control" id="transfer_quantity"
|
|
name="transfer_quantity" min="0.01" step="0.01" required>
|
|
<span class="input-group-text" id="transfer-quantity-unit">units</span>
|
|
</div>
|
|
<small class="form-text text-muted">
|
|
Maximum: <span id="max-transfer-quantity">0</span> units
|
|
</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="form-group mb-3">
|
|
<label class="form-label" for="transfer_reason">
|
|
Transfer Reason <span class="text-danger">*</span>
|
|
</label>
|
|
<select class="form-select" id="transfer_reason" name="transfer_reason" required>
|
|
<option value="">Select reason...</option>
|
|
<option value="restock">Restock Location</option>
|
|
<option value="redistribution">Stock Redistribution</option>
|
|
<option value="patient_care">Patient Care Need</option>
|
|
<option value="emergency">Emergency Request</option>
|
|
<option value="maintenance">Equipment Maintenance</option>
|
|
<option value="consolidation">Stock Consolidation</option>
|
|
<option value="other">Other</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="form-group mb-3">
|
|
<label class="form-label" for="transfer_priority">
|
|
Priority
|
|
</label>
|
|
<select class="form-select" id="transfer_priority" name="transfer_priority">
|
|
<option value="normal">Normal</option>
|
|
<option value="urgent">Urgent</option>
|
|
<option value="emergency">Emergency</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="form-group mb-3">
|
|
<label class="form-label" for="transfer_requested_by">
|
|
Requested By
|
|
</label>
|
|
<select class="form-select" id="transfer_requested_by" name="transfer_requested_by">
|
|
<option value="">Select staff member...</option>
|
|
{% for staff in hospital_staff %}
|
|
<option value="{{ staff.id }}" {% if staff.id == request.user.id %}selected{% endif %}>
|
|
{{ staff.get_full_name }} - {{ staff.department }}
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="form-group mb-3">
|
|
<label class="form-label" for="transfer_reference">
|
|
Reference Number
|
|
</label>
|
|
<input type="text" class="form-control" id="transfer_reference"
|
|
name="transfer_reference" placeholder="Optional reference">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="form-group mb-3">
|
|
<label class="form-label" for="transfer_notes">
|
|
Transfer Notes
|
|
</label>
|
|
<textarea class="form-control" id="transfer_notes" name="transfer_notes"
|
|
rows="3" placeholder="Additional notes about this transfer..."></textarea>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Transfer Summary -->
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="card border-info" id="transfer-summary" style="display: none;">
|
|
<div class="card-header bg-info text-white">
|
|
<h6 class="card-title mb-0">
|
|
<i class="fas fa-clipboard-list me-2"></i>Transfer Summary
|
|
</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="transfer-detail">
|
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
|
<strong>From:</strong>
|
|
<span id="summary-from-location">Source Location</span>
|
|
</div>
|
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
|
<strong>To:</strong>
|
|
<span id="summary-to-location">Destination Location</span>
|
|
</div>
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<strong>Quantity:</strong>
|
|
<span id="summary-quantity">0 units</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="stock-impact">
|
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
|
<span>Remaining at Source:</span>
|
|
<span class="fw-bold" id="summary-remaining">0 units</span>
|
|
</div>
|
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
|
<span>New Stock at Destination:</span>
|
|
<span class="fw-bold text-success" id="summary-new-stock">0 units</span>
|
|
</div>
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<span>Transfer Value:</span>
|
|
<span class="fw-bold text-primary" id="summary-value">$0.00</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Warnings -->
|
|
<div id="transfer-warnings" style="display: none;">
|
|
<div class="alert alert-warning">
|
|
<i class="fas fa-exclamation-triangle me-2"></i>
|
|
<span id="transfer-warning-message">Warning message</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Approval Required -->
|
|
<div id="approval-required" style="display: none;">
|
|
<div class="alert alert-info">
|
|
<i class="fas fa-user-check me-2"></i>
|
|
This transfer requires supervisor approval due to quantity or priority level.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
|
<i class="fas fa-times me-1"></i>Cancel
|
|
</button>
|
|
<button type="button" class="btn btn-outline-info me-2" id="save-draft-btn" style="display: none;">
|
|
<i class="fas fa-save me-1"></i>Save as Draft
|
|
</button>
|
|
<button type="submit" class="btn btn-primary" id="transfer-submit-btn" disabled>
|
|
<i class="fas fa-exchange-alt me-1"></i>
|
|
<span id="submit-btn-text">Submit Transfer</span>
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
$(document).ready(function() {
|
|
// Initialize transfer stock modal
|
|
$('#transferStockModal').on('show.bs.modal', function(event) {
|
|
const button = $(event.relatedTarget);
|
|
const itemId = button.data('item-id');
|
|
const itemName = button.data('item-name');
|
|
const itemCode = button.data('item-code');
|
|
const itemCategory = button.data('item-category');
|
|
const fromLocation = button.data('from-location');
|
|
const availableStock = button.data('available-stock');
|
|
const stockUnit = button.data('stock-unit');
|
|
const unitValue = button.data('unit-value') || 0;
|
|
|
|
// Update modal content
|
|
$('#transfer-item-name').text(itemName);
|
|
$('#transfer-item-code').text(itemCode);
|
|
$('#transfer-item-category').text(itemCategory);
|
|
$('#transfer-from-location').text(fromLocation);
|
|
$('#transfer-available-stock').text(availableStock);
|
|
$('#transfer-stock-unit').text(stockUnit);
|
|
$('#transfer-quantity-unit').text(stockUnit);
|
|
$('#max-transfer-quantity').text(availableStock);
|
|
|
|
// Store item data
|
|
$('#transferStockForm').data('item-id', itemId);
|
|
$('#transferStockForm').data('from-location', fromLocation);
|
|
$('#transferStockForm').data('available-stock', availableStock);
|
|
$('#transferStockForm').data('stock-unit', stockUnit);
|
|
$('#transferStockForm').data('unit-value', unitValue);
|
|
|
|
// Set max quantity
|
|
$('#transfer_quantity').attr('max', availableStock);
|
|
|
|
// Reset form
|
|
$('#transferStockForm')[0].reset();
|
|
$('#transfer-summary').hide();
|
|
$('#transfer-warnings').hide();
|
|
$('#approval-required').hide();
|
|
$('#transfer-submit-btn').prop('disabled', true);
|
|
});
|
|
|
|
// Handle input changes for validation and summary
|
|
$('#transfer_to_location, #transfer_quantity, #transfer_reason, #transfer_priority').on('input change', function() {
|
|
updateTransferSummary();
|
|
validateTransferForm();
|
|
});
|
|
|
|
// Handle quantity validation
|
|
$('#transfer_quantity').on('input', function() {
|
|
const quantity = parseFloat($(this).val()) || 0;
|
|
const available = parseFloat($('#transferStockForm').data('available-stock')) || 0;
|
|
|
|
if (quantity > available) {
|
|
$(this).val(available);
|
|
$('#transfer-warning-message').text('Quantity cannot exceed available stock.');
|
|
$('#transfer-warnings').show();
|
|
} else {
|
|
$('#transfer-warnings').hide();
|
|
}
|
|
});
|
|
|
|
// Handle priority change for approval requirements
|
|
$('#transfer_priority').on('change', function() {
|
|
const priority = $(this).val();
|
|
const quantity = parseFloat($('#transfer_quantity').val()) || 0;
|
|
|
|
if (priority === 'emergency' || quantity > 100) { // Example threshold
|
|
$('#approval-required').show();
|
|
$('#submit-btn-text').text('Submit for Approval');
|
|
$('#save-draft-btn').show();
|
|
} else {
|
|
$('#approval-required').hide();
|
|
$('#submit-btn-text').text('Submit Transfer');
|
|
$('#save-draft-btn').hide();
|
|
}
|
|
});
|
|
|
|
// Form submission
|
|
$('#transferStockForm').on('submit', function(e) {
|
|
e.preventDefault();
|
|
|
|
const formData = new FormData(this);
|
|
const itemId = $(this).data('item-id');
|
|
|
|
// Add item ID to form data
|
|
formData.append('item_id', itemId);
|
|
|
|
// Show loading state
|
|
const submitBtn = $('#transfer-submit-btn');
|
|
const originalText = submitBtn.html();
|
|
submitBtn.html('<i class="fas fa-spinner fa-spin me-1"></i>Processing...').prop('disabled', true);
|
|
|
|
$.ajax({
|
|
url: '',
|
|
method: 'POST',
|
|
data: formData,
|
|
processData: false,
|
|
contentType: false,
|
|
success: function(response) {
|
|
if (response.success) {
|
|
// Show success message
|
|
const message = response.requires_approval ?
|
|
'Transfer request submitted for approval' :
|
|
'Stock transferred successfully';
|
|
showToast('success', message);
|
|
|
|
// Close modal
|
|
$('#transferStockModal').modal('hide');
|
|
|
|
// Refresh page or update stock display
|
|
if (typeof refreshStockData === 'function') {
|
|
refreshStockData();
|
|
} else {
|
|
location.reload();
|
|
}
|
|
} else {
|
|
showToast('error', response.message || 'Error processing transfer');
|
|
}
|
|
},
|
|
error: function(xhr) {
|
|
let errorMessage = 'Error processing transfer';
|
|
if (xhr.responseJSON && xhr.responseJSON.message) {
|
|
errorMessage = xhr.responseJSON.message;
|
|
}
|
|
showToast('error', errorMessage);
|
|
},
|
|
complete: function() {
|
|
// Restore button state
|
|
submitBtn.html(originalText).prop('disabled', false);
|
|
}
|
|
});
|
|
});
|
|
|
|
// Save as draft functionality
|
|
$('#save-draft-btn').on('click', function() {
|
|
// Similar to form submission but with draft status
|
|
const formData = new FormData(document.getElementById('transferStockForm'));
|
|
formData.append('save_as_draft', 'true');
|
|
|
|
// Process draft save...
|
|
showToast('info', 'Transfer saved as draft');
|
|
$('#transferStockModal').modal('hide');
|
|
});
|
|
});
|
|
|
|
function updateTransferSummary() {
|
|
const toLocationSelect = $('#transfer_to_location');
|
|
const toLocationName = toLocationSelect.find('option:selected').data('location-name');
|
|
const quantity = parseFloat($('#transfer_quantity').val()) || 0;
|
|
const fromLocation = $('#transferStockForm').data('from-location');
|
|
const availableStock = parseFloat($('#transferStockForm').data('available-stock')) || 0;
|
|
const stockUnit = $('#transferStockForm').data('stock-unit');
|
|
const unitValue = parseFloat($('#transferStockForm').data('unit-value')) || 0;
|
|
|
|
if (toLocationName && quantity > 0) {
|
|
const remaining = availableStock - quantity;
|
|
const transferValue = quantity * unitValue;
|
|
|
|
// Update summary
|
|
$('#summary-from-location').text(fromLocation);
|
|
$('#summary-to-location').text(toLocationName);
|
|
$('#summary-quantity').text(quantity + ' ' + stockUnit);
|
|
$('#summary-remaining').text(remaining + ' ' + stockUnit);
|
|
$('#summary-new-stock').text(quantity + ' ' + stockUnit); // Simplified - would need destination current stock
|
|
$('#summary-value').text('$' + transferValue.toFixed(2));
|
|
|
|
// Show summary
|
|
$('#transfer-summary').show();
|
|
} else {
|
|
$('#transfer-summary').hide();
|
|
}
|
|
}
|
|
|
|
function validateTransferForm() {
|
|
const toLocation = $('#transfer_to_location').val();
|
|
const quantity = parseFloat($('#transfer_quantity').val()) || 0;
|
|
const reason = $('#transfer_reason').val();
|
|
const available = parseFloat($('#transferStockForm').data('available-stock')) || 0;
|
|
|
|
const isValid = toLocation && quantity > 0 && quantity <= available && reason;
|
|
|
|
$('#transfer-submit-btn').prop('disabled', !isValid);
|
|
|
|
if (isValid) {
|
|
$('#transfer-submit-btn').removeClass('btn-outline-primary').addClass('btn-primary');
|
|
} else {
|
|
$('#transfer-submit-btn').removeClass('btn-primary').addClass('btn-outline-primary');
|
|
}
|
|
}
|
|
|
|
function showToast(type, message) {
|
|
// Simple toast notification
|
|
let toastClass;
|
|
switch(type) {
|
|
case 'success':
|
|
toastClass = 'alert-success';
|
|
break;
|
|
case 'error':
|
|
toastClass = 'alert-danger';
|
|
break;
|
|
case 'info':
|
|
toastClass = 'alert-info';
|
|
break;
|
|
default:
|
|
toastClass = 'alert-secondary';
|
|
}
|
|
|
|
const toastHtml = `
|
|
<div class="alert ${toastClass} alert-dismissible fade show position-fixed"
|
|
style="top: 20px; right: 20px; z-index: 9999;">
|
|
${message}
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
</div>
|
|
`;
|
|
$('body').append(toastHtml);
|
|
|
|
// Auto-remove after 5 seconds
|
|
setTimeout(function() {
|
|
$('.alert').fadeOut();
|
|
}, 5000);
|
|
}
|
|
</script>
|
|
|
|
<style>
|
|
.current-stock {
|
|
text-align: center;
|
|
padding: 10px;
|
|
background: #f8f9fa;
|
|
border-radius: 5px;
|
|
}
|
|
|
|
.transfer-detail, .stock-impact {
|
|
padding: 10px;
|
|
background: #f8f9fa;
|
|
border-radius: 5px;
|
|
}
|
|
|
|
#transfer-summary .card-body {
|
|
padding: 15px;
|
|
}
|
|
|
|
.modal-lg {
|
|
max-width: 800px;
|
|
}
|
|
|
|
/* Priority badges */
|
|
.priority-normal { color: #28a745; }
|
|
.priority-urgent { color: #ffc107; }
|
|
.priority-emergency { color: #dc3545; }
|
|
|
|
/* Mobile responsive */
|
|
@media (max-width: 768px) {
|
|
.transfer-detail, .stock-impact {
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.d-flex.justify-content-between {
|
|
flex-direction: column;
|
|
align-items: flex-start !important;
|
|
}
|
|
|
|
.d-flex.justify-content-between span:last-child {
|
|
font-weight: bold;
|
|
margin-top: 5px;
|
|
}
|
|
}
|
|
</style>
|
|
|