hospital-management/templates/billing/bulk_submit_bills.html
2025-08-12 13:33:25 +03:00

516 lines
19 KiB
HTML

{% extends 'base/base.html' %}
{% load static %}
{% block title %}Bulk Submit Bills - {{ block.super }}{% endblock %}
{% block css %}
<style>
.bill-validation-error {
color: #dc3545;
font-size: 0.875rem;
}
.bill-validation-success {
color: #28a745;
font-size: 0.875rem;
}
.bill-row {
transition: background-color 0.2s;
}
.bill-row:hover {
background-color: #f8f9fa;
}
.bill-row.selected {
background-color: #e3f2fd;
}
.validation-badge {
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
font-size: 0.75rem;
font-weight: 600;
}
.validation-valid {
background-color: #d4edda;
color: #155724;
}
.validation-invalid {
background-color: #f8d7da;
color: #721c24;
}
.bulk-actions {
position: sticky;
top: 0;
background: white;
z-index: 100;
border-bottom: 1px solid #dee2e6;
padding: 1rem 0;
margin-bottom: 1rem;
}
.progress-container {
display: none;
margin-top: 1rem;
}
.results-container {
display: none;
margin-top: 1rem;
}
.result-item {
padding: 0.5rem;
margin: 0.25rem 0;
border-radius: 0.25rem;
}
.result-success {
background-color: #d4edda;
border: 1px solid #c3e6cb;
color: #155724;
}
.result-error {
background-color: #f8d7da;
border: 1px solid #f5c6cb;
color: #721c24;
}
.result-warning {
background-color: #fff3cd;
border: 1px solid #ffeaa7;
color: #856404;
}
</style>
{% endblock %}
{% block content %}
<div class="container-fluid">
<!-- Page Header -->
<div class="row">
<div class="col-12">
<div class="page-header">
<div class="page-title">
<h4>Bulk Submit Bills</h4>
<h6>Submit multiple draft bills for processing</h6>
</div>
<div class="page-btn">
<a href="{% url 'billing:bill_list' %}" class="btn btn-secondary">
<i class="fas fa-arrow-left"></i> Back to Bills
</a>
</div>
</div>
</div>
</div>
<!-- Summary Cards -->
<div class="row mb-4">
<div class="col-md-3">
<div class="card bg-primary text-white">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h4 class="mb-0">{{ total_draft_bills }}</h4>
<p class="mb-0">Total Draft Bills</p>
</div>
<div class="align-self-center">
<i class="fas fa-file-invoice fa-2x"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-success text-white">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h4 class="mb-0">{{ submittable_bills }}</h4>
<p class="mb-0">Ready to Submit</p>
</div>
<div class="align-self-center">
<i class="fas fa-check-circle fa-2x"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-warning text-white">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h4 class="mb-0">{{ total_draft_bills|add:"-"|add:submittable_bills }}</h4>
<p class="mb-0">Need Attention</p>
</div>
<div class="align-self-center">
<i class="fas fa-exclamation-triangle fa-2x"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-info text-white">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h4 class="mb-0" id="selected-count">0</h4>
<p class="mb-0">Selected Bills</p>
</div>
<div class="align-self-center">
<i class="fas fa-tasks fa-2x"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Bulk Actions -->
<div class="card">
<div class="card-body">
<div class="bulk-actions">
<div class="row align-items-center">
<div class="col-md-6">
<div class="form-check">
<input type="checkbox" class="form-check-input" id="select-all">
<label class="form-check-label" for="select-all">
Select All Valid Bills
</label>
</div>
</div>
<div class="col-md-6 text-end">
<button type="button" class="btn btn-primary" id="bulk-submit-btn" disabled>
<i class="fas fa-paper-plane"></i> Submit Selected Bills
</button>
<button type="button" class="btn btn-outline-secondary" id="select-valid-btn">
<i class="fas fa-check"></i> Select All Valid
</button>
<button type="button" class="btn btn-outline-secondary" id="clear-selection-btn">
<i class="fas fa-times"></i> Clear Selection
</button>
</div>
</div>
</div>
<!-- Progress Bar -->
<div class="progress-container">
<div class="alert alert-info">
<i class="fas fa-spinner fa-spin"></i> Submitting bills, please wait...
</div>
<div class="progress">
<div class="progress-bar progress-bar-striped progress-bar-animated"
role="progressbar" style="width: 0%"></div>
</div>
</div>
<!-- Results Container -->
<div class="results-container">
<h5>Submission Results</h5>
<div id="results-content"></div>
</div>
<!-- Bills Table -->
{% if bills_with_status %}
<div class="table-responsive">
<table class="table table-hover">
<thead class="table-dark">
<tr>
<th width="50">
<input type="checkbox" id="header-checkbox" class="form-check-input">
</th>
<th>Bill Number</th>
<th>Patient</th>
<th>Bill Date</th>
<th>Total Amount</th>
<th>Status</th>
<th>Validation</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for item in bills_with_status %}
<tr class="bill-row" data-bill-id="{{ item.bill.bill_id }}"
data-can-submit="{{ item.can_submit|yesno:'true,false' }}">
<td>
{% if item.can_submit %}
<input type="checkbox" class="form-check-input bill-checkbox"
value="{{ item.bill.bill_id }}">
{% else %}
<input type="checkbox" class="form-check-input" disabled>
{% endif %}
</td>
<td>
<strong>{{ item.bill.bill_number }}</strong>
</td>
<td>
{% if item.bill.patient %}
{{ item.bill.patient.get_full_name }}
{% else %}
<span class="text-muted">No patient assigned</span>
{% endif %}
</td>
<td>
{% if item.bill.bill_date %}
{{ item.bill.bill_date|date:"M d, Y" }}
{% else %}
<span class="text-muted">No date</span>
{% endif %}
</td>
<td>
{% if item.bill.total_amount %}
${{ item.bill.total_amount|floatformat:2 }}
{% else %}
<span class="text-muted">$0.00</span>
{% endif %}
</td>
<td>
<span class="badge bg-warning">{{ item.bill.get_status_display }}</span>
</td>
<td>
{% if item.can_submit %}
<span class="validation-badge validation-valid">
<i class="fas fa-check"></i> Valid
</span>
{% else %}
<span class="validation-badge validation-invalid">
<i class="fas fa-exclamation-triangle"></i> Issues
</span>
<div class="bill-validation-error mt-1">
{% for error in item.validation_errors %}
<small>• {{ error }}</small><br>
{% endfor %}
</div>
{% endif %}
</td>
<td>
<div class="btn-group btn-group-sm">
<a href="{% url 'billing:bill_detail' item.bill.bill_id %}"
class="btn btn-outline-primary" title="View Details">
<i class="fas fa-eye"></i>
</a>
<a href="{% url 'billing:bill_update' item.bill.bill_id %}"
class="btn btn-outline-secondary" title="Edit">
<i class="fas fa-edit"></i>
</a>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="text-center py-5">
<i class="fas fa-inbox fa-3x text-muted mb-3"></i>
<h5 class="text-muted">No Draft Bills Found</h5>
<p class="text-muted">There are no draft bills available for submission.</p>
<a href="{% url 'billing:bill_create' %}" class="btn btn-primary">
<i class="fas fa-plus"></i> Create New Bill
</a>
</div>
{% endif %}
</div>
</div>
</div>
{% endblock %}
{% block js %}
<script>
$(document).ready(function() {
let selectedBills = [];
// Update selected count
function updateSelectedCount() {
const count = selectedBills.length;
$('#selected-count').text(count);
$('#bulk-submit-btn').prop('disabled', count === 0);
}
// Handle individual checkbox changes
$('.bill-checkbox').change(function() {
const billId = $(this).val();
const isChecked = $(this).is(':checked');
if (isChecked) {
if (!selectedBills.includes(billId)) {
selectedBills.push(billId);
}
$(this).closest('tr').addClass('selected');
} else {
selectedBills = selectedBills.filter(id => id !== billId);
$(this).closest('tr').removeClass('selected');
}
updateSelectedCount();
// Update header checkbox
const totalCheckboxes = $('.bill-checkbox').length;
const checkedCheckboxes = $('.bill-checkbox:checked').length;
$('#header-checkbox').prop('indeterminate', checkedCheckboxes > 0 && checkedCheckboxes < totalCheckboxes);
$('#header-checkbox').prop('checked', checkedCheckboxes === totalCheckboxes);
});
// Handle header checkbox
$('#header-checkbox').change(function() {
const isChecked = $(this).is(':checked');
$('.bill-checkbox').prop('checked', isChecked).trigger('change');
});
// Select all valid bills
$('#select-valid-btn').click(function() {
$('.bill-checkbox').prop('checked', true).trigger('change');
});
// Clear selection
$('#clear-selection-btn').click(function() {
$('.bill-checkbox').prop('checked', false).trigger('change');
});
// Bulk submit
$('#bulk-submit-btn').click(function() {
if (selectedBills.length === 0) {
alert('Please select at least one bill to submit.');
return;
}
if (!confirm(`Are you sure you want to submit ${selectedBills.length} bill(s)?`)) {
return;
}
// Show progress
$('.progress-container').show();
$('.results-container').hide();
$(this).prop('disabled', true);
// Prepare form data
const formData = new FormData();
selectedBills.forEach(billId => {
formData.append('bill_ids', billId);
});
// Add CSRF token
formData.append('csrfmiddlewaretoken', $('[name=csrfmiddlewaretoken]').val());
// Submit request
$.ajax({
url: '{% url "billing:bulk_submit_bills" %}',
type: 'POST',
data: formData,
processData: false,
contentType: false,
success: function(response) {
$('.progress-container').hide();
displayResults(response);
// Refresh page after successful submission
if (response.success && response.summary.total_submitted > 0) {
setTimeout(() => {
window.location.reload();
}, 3000);
}
},
error: function(xhr, status, error) {
$('.progress-container').hide();
alert('An error occurred while submitting bills: ' + error);
},
complete: function() {
$('#bulk-submit-btn').prop('disabled', false);
}
});
});
// Display results
function displayResults(response) {
const resultsContent = $('#results-content');
resultsContent.empty();
// Summary
const summary = response.summary;
resultsContent.append(`
<div class="alert alert-info">
<h6>Summary</h6>
<ul class="mb-0">
<li>Total Processed: ${summary.total_processed}</li>
<li>Successfully Submitted: ${summary.total_submitted}</li>
<li>Failed: ${summary.total_failed}</li>
<li>Already Submitted: ${summary.total_already_submitted}</li>
</ul>
</div>
`);
// Successful submissions
if (response.results.submitted.length > 0) {
resultsContent.append('<h6>Successfully Submitted:</h6>');
response.results.submitted.forEach(bill => {
resultsContent.append(`
<div class="result-item result-success">
<strong>${bill.bill_number}</strong> - ${bill.patient_name} ($${bill.total_amount})
</div>
`);
});
}
// Failed submissions
if (response.results.failed.length > 0) {
resultsContent.append('<h6>Failed Submissions:</h6>');
response.results.failed.forEach(bill => {
resultsContent.append(`
<div class="result-item result-error">
<strong>${bill.bill_number}</strong> - ${bill.errors.join(', ')}
</div>
`);
});
}
// Already submitted
if (response.results.already_submitted.length > 0) {
resultsContent.append('<h6>Already Submitted:</h6>');
response.results.already_submitted.forEach(bill => {
resultsContent.append(`
<div class="result-item result-warning">
<strong>${bill.bill_number}</strong> - Status: ${bill.status}
</div>
`);
});
}
$('.results-container').show();
}
// Get CSRF token
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
// Set up CSRF token for AJAX
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url))) {
xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
}
}
});
});
</script>
{% endblock %}