recreate project
This commit is contained in:
@@ -0,0 +1,316 @@
|
||||
<?= $this->extend('layout/management_layout') ?>
|
||||
<?= $this->section('content') ?>
|
||||
|
||||
<div class="container-fluid mt-4">
|
||||
<h2 class="text-center mt-4 mb-3">Refunds</h2>
|
||||
|
||||
<!-- Flash messages -->
|
||||
<?php if (session()->getFlashdata('success')): ?>
|
||||
<div class="alert alert-success"><?= session()->getFlashdata('success') ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if (session()->getFlashdata('error')): ?>
|
||||
<div class="alert alert-danger"><?= session()->getFlashdata('error') ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if (session()->getFlashdata('info')): ?>
|
||||
<div class="alert alert-info mb-3 d-inline-block"><?= session()->getFlashdata('info') ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- (Optional) Bulk calculation form kept for future use -->
|
||||
<form method="post" action="/refunds/processRefunds">
|
||||
<?= csrf_field() ?>
|
||||
<?php
|
||||
$submitted = [];
|
||||
foreach ($refunds as $refund):
|
||||
if (!empty($refund['request']) && !in_array($refund['parent_id'], $submitted, true)):
|
||||
$submitted[] = $refund['parent_id'];
|
||||
?>
|
||||
<input type="hidden" name="parent_ids[]" value="<?= esc($refund['parent_id']) ?>">
|
||||
<?php
|
||||
endif;
|
||||
endforeach;
|
||||
?>
|
||||
<?php if (empty($submitted)): ?>
|
||||
<div class="alert alert-info mb-3 d-inline-block">No eligible parents for refund calculation.</div>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
|
||||
<div class="d-flex justify-content-end mb-2 gap-2 flex-wrap">
|
||||
<form method="post" action="/refunds/recalculateOverpayments" class="d-flex gap-2">
|
||||
<?= csrf_field() ?>
|
||||
<button class="btn btn-outline-primary btn-sm" type="submit" title="Current school year">
|
||||
Recalculate Overpayments (This Year)
|
||||
</button>
|
||||
</form>
|
||||
<form method="post" action="/refunds/recalculateOverpayments" class="d-flex gap-2">
|
||||
<?= csrf_field() ?>
|
||||
<input type="text" name="invoice_number" class="form-control form-control-sm" placeholder="Invoice # (e.g., INV-...)" style="min-width: 260px;">
|
||||
<button class="btn btn-outline-secondary btn-sm" type="submit" title="Recalc a specific invoice across any year">
|
||||
Recalc Specific Invoice
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table id="refundsTable" class="table table-bordered table-striped align-middle w-100">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>School ID</th>
|
||||
<th>Parent</th>
|
||||
<th>Request</th>
|
||||
<th>Term</th>
|
||||
<th>Invoice ID</th>
|
||||
<th class="text-end">Refund Amount</th>
|
||||
<th>Status</th>
|
||||
<th>Requested</th>
|
||||
<th>Approved</th>
|
||||
<th>Approved By</th>
|
||||
<th>Refunded</th>
|
||||
<th>Method</th>
|
||||
<th>Check #</th>
|
||||
<th>Check File</th>
|
||||
<th class="text-end">Paid Amount</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($refunds as $r): ?>
|
||||
<?php
|
||||
// Friendly badges
|
||||
$req = strtolower((string)($r['request'] ?? ''));
|
||||
$reqBadgeClass = [
|
||||
'tuition' => 'primary',
|
||||
'overpayment' => 'success',
|
||||
'extra' => 'info',
|
||||
'duplicate' => 'warning',
|
||||
][$req] ?? 'secondary';
|
||||
|
||||
// Date formatting (keep raw if null)
|
||||
$fmt = function($dt) {
|
||||
if (empty($dt) || $dt === '0000-00-00 00:00:00') return '-';
|
||||
// show date only; assumes DB is UTC
|
||||
return local_date($dt, 'm-d-Y');
|
||||
};
|
||||
?>
|
||||
<tr>
|
||||
<td><?= esc($r['school_id']) ?></td>
|
||||
<td><?= esc(($r['firstname'] ?? '').' '.($r['lastname'] ?? '')) ?></td>
|
||||
<td>
|
||||
<?php if ($req): ?>
|
||||
<span class="badge bg-<?= $reqBadgeClass ?> text-uppercase"><?= esc($req) ?></span>
|
||||
<?php else: ?>
|
||||
<span class="text-muted">-</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td><?= esc(($r['school_year'] ?? '-'). ' / ' . ($r['semester'] ?? '-')) ?></td>
|
||||
<td><?= esc($r['invoice_id'] ?? '-') ?></td>
|
||||
<td class="text-end">$<?= esc(number_format((float)$r['refund_amount'], 2)) ?></td>
|
||||
<td><?= esc($r['status']) ?></td>
|
||||
<td><?= esc($fmt($r['requested_at'] ?? null)) ?></td>
|
||||
<td><?= esc($fmt($r['approved_at'] ?? null)) ?></td>
|
||||
<td><?= esc($r['approved_by_name'] ?? '-') ?></td>
|
||||
<td><?= esc($fmt($r['refunded_at'] ?? null)) ?></td>
|
||||
<td><?= esc($r['refund_method'] ?? '-') ?></td>
|
||||
<td><?= esc($r['check_nbr'] ?? '-') ?></td>
|
||||
<td>
|
||||
<?php if (!empty($r['check_file'])): ?>
|
||||
<a href="<?= base_url('uploads/checks/' . $r['check_file']) ?>" target="_blank">View</a>
|
||||
<?php else: ?>
|
||||
-
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="text-end">$<?= esc(number_format((float)$r['refund_paid_amount'], 2)) ?></td>
|
||||
<td class="d-flex gap-2">
|
||||
<button class="btn btn-success btn-sm"
|
||||
onclick="handleRecordRefundClick(<?= (int)$r['id'] ?>, '<?= esc($r['status']) ?>', <?= (float)$r['refund_amount'] ?>)">
|
||||
Record
|
||||
</button>
|
||||
<button class="btn btn-primary btn-sm"
|
||||
onclick="handleStatusClick(<?= (int)$r['id'] ?>, 'Approved', <?= (float)$r['refund_amount'] ?>)">
|
||||
Approve
|
||||
</button>
|
||||
<button class="btn btn-danger btn-sm"
|
||||
onclick="handleStatusClick(<?= (int)$r['id'] ?>, 'Rejected', <?= (float)$r['refund_amount'] ?>)">
|
||||
Reject
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal -->
|
||||
<div class="modal fade" id="statusModal" tabindex="-1" aria-labelledby="statusModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
|
||||
<!-- APPROVE / REJECT FORM -->
|
||||
<form id="statusForm" method="post" action="/refunds/updateStatus" class="d-none">
|
||||
<?= csrf_field() ?>
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Update Refund Status</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<input type="hidden" name="refund_id" id="refundIdStatus">
|
||||
<input type="hidden" name="status" id="newStatus">
|
||||
<p id="confirmationText" class="mb-2">
|
||||
Please provide a reason to <strong id="statusText"></strong> this refund.
|
||||
</p>
|
||||
<div class="mb-3">
|
||||
<label for="reason" class="form-label">Reason <span class="text-danger">*</span></label>
|
||||
<textarea class="form-control" name="reason" id="reason" rows="3" required></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Confirm</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- PAYMENT FORM -->
|
||||
<form id="paymentForm" method="post" action="/refunds/updatePayment" enctype="multipart/form-data" class="d-none">
|
||||
<?= csrf_field() ?>
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Record Refund</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<input type="hidden" name="refund_id" id="refundIdPayment">
|
||||
<div class="mb-3">
|
||||
<label for="paidAmount" class="form-label">Paid Amount</label>
|
||||
<input type="number" step="0.01" min="0.01" class="form-control" name="paid_amount" id="paidAmount" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="paymentMethod" class="form-label">Payment Method</label>
|
||||
<select class="form-select" name="payment_method" id="paymentMethod" required>
|
||||
<option value="">-- Select Method --</option>
|
||||
<option value="Check">Check</option>
|
||||
<option value="Online">Online</option>
|
||||
<option value="Cash">Cash</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div id="checkDetails" class="d-none">
|
||||
<div class="mb-3">
|
||||
<label for="checkNumber" class="form-label">Check Number</label>
|
||||
<input type="text" class="form-control" name="check_number" id="checkNumber">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="checkFile" class="form-label">Upload Check Image</label>
|
||||
<input type="file" class="form-control" name="check_file" id="checkFile" accept="image/*">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Submit Payment</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
<?= $this->section('scripts') ?>
|
||||
<script>
|
||||
$(function () {
|
||||
// DataTable
|
||||
$('#refundsTable').DataTable({
|
||||
pageLength: 25,
|
||||
order: [[7, 'desc']], // order by Requested desc
|
||||
scrollX: true,
|
||||
autoWidth: false,
|
||||
});
|
||||
|
||||
// Status form (approve/reject)
|
||||
$('#statusForm').on('submit', function (e) {
|
||||
e.preventDefault();
|
||||
const reason = $('#reason').val().trim();
|
||||
if (!reason) {
|
||||
alert('Reason is required.');
|
||||
return;
|
||||
}
|
||||
$.post($(this).attr('action'), $(this).serialize())
|
||||
.done(function (resp) { alert(resp.success || resp.error); location.reload(); })
|
||||
.fail(function () { alert('An error occurred. Please try again.'); });
|
||||
});
|
||||
|
||||
// Payment form
|
||||
$('#paymentForm').on('submit', function (e) {
|
||||
e.preventDefault();
|
||||
const fd = new FormData(this);
|
||||
$.ajax({
|
||||
url: $(this).attr('action'),
|
||||
method: 'POST',
|
||||
data: fd,
|
||||
processData: false,
|
||||
contentType: false
|
||||
})
|
||||
.done(function (resp) { alert(resp.success || resp.error); location.reload(); })
|
||||
.fail(function () { alert('An error occurred. Please try again.'); });
|
||||
});
|
||||
|
||||
// Toggle check details by method
|
||||
$('#paymentMethod').on('change', function () {
|
||||
if ($(this).val() === 'Check') {
|
||||
$('#checkDetails').removeClass('d-none');
|
||||
} else {
|
||||
$('#checkDetails').addClass('d-none');
|
||||
$('#checkNumber').val('');
|
||||
$('#checkFile').val('');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ---- Actions ----
|
||||
function handleRecordRefundClick(refundId, status, amount) {
|
||||
if (!['Approved','Partial'].includes(status)) {
|
||||
alert('⚠ Refund must be Approved (or Partial) before recording a payout.');
|
||||
return;
|
||||
}
|
||||
if (!amount || parseFloat(amount) <= 0) {
|
||||
alert('⚠ Refund amount not set.');
|
||||
return;
|
||||
}
|
||||
showPaymentModal(refundId);
|
||||
}
|
||||
|
||||
function handleStatusClick(refundId, status, amount) {
|
||||
if (!amount || parseFloat(amount) <= 0) {
|
||||
alert('⚠ Refund amount not set.');
|
||||
return;
|
||||
}
|
||||
showStatusModal(refundId, status);
|
||||
}
|
||||
|
||||
function showStatusModal(refundId, status) {
|
||||
$('#paymentForm').addClass('d-none');
|
||||
$('#statusForm').removeClass('d-none');
|
||||
|
||||
$('#refundIdStatus').val(refundId);
|
||||
$('#newStatus').val(status);
|
||||
$('#reason').val('');
|
||||
$('#statusText').text(status.toLowerCase());
|
||||
$('#statusModal').modal('show');
|
||||
}
|
||||
|
||||
function showPaymentModal(refundId) {
|
||||
$('#statusForm').addClass('d-none');
|
||||
$('#paymentForm').removeClass('d-none');
|
||||
|
||||
$('#refundIdPayment').val(refundId);
|
||||
$('#paidAmount').val('');
|
||||
$('#paymentMethod').val('').trigger('change');
|
||||
$('#checkDetails').addClass('d-none');
|
||||
$('#checkNumber').val('');
|
||||
$('#checkFile').val('');
|
||||
|
||||
$('#statusModal').modal('show');
|
||||
}
|
||||
</script>
|
||||
<?= $this->endSection() ?>
|
||||
Reference in New Issue
Block a user