Files
alrahma_sunday_school/app/Views/payment/manual_payment.php
T
2026-06-01 02:08:27 -04:00

581 lines
23 KiB
PHP

<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container mt-4">
<h2 class="text-center mt-4 mb-3">Manual Payment</h2>
<?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; ?>
<!-- Search form -->
<form method="get" action="<?= base_url('payment/manual_pay') ?>" class="mb-3">
<div class="input-group">
<input type="text" name="search_term" class="form-control" placeholder="Enter Parent Email or Phone" required value="<?= esc($searchTermUsedInSearch ?? '') ?>">
<button class="btn btn-primary" type="submit">Search</button>
</div>
</form>
<?php if ($parent): ?>
<!-- Parent + Student info -->
<div class="row mb-4">
<div class="col-md-6">
<div class="card">
<div class="card-header">Parent Information</div>
<div class="card-body">
<p><strong>Name:</strong> <?= esc($parent['firstname']) ?> <?= esc($parent['lastname']) ?></p>
<p><strong>Phone:</strong> <?= esc($parent['cellphone']) ?></p>
<p><strong>Email:</strong> <?= esc($parent['email']) ?></p>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-header">Students</div>
<div class="card-body">
<?php if (!empty($students)): ?>
<ul class="list-group">
<?php foreach ($students as $student): ?>
<li class="list-group-item"><?= esc($student['firstname']) ?> <?= esc($student['lastname']) ?></li>
<?php endforeach; ?>
</ul>
<?php else: ?>
<p>No students found.</p>
<?php endif; ?>
</div>
</div>
</div>
</div>
<!-- Payments table -->
<h5>Payments</h5>
<table class="table table-bordered">
<thead>
<tr>
<th>Paid Date</th>
<th>Paid Amount</th>
<th>Balance</th>
<th>Payment Method</th>
<th>Check Number</th>
<th>Installments</th>
<th>Status</th>
<th>Check/Card File</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<?php foreach ($payments as $payment): ?>
<tr>
<td><?= $payment['payment_date']->format('m-d-Y h:i A') ?></td>
<td>$<?= number_format((float)$payment['paid_amount'], 2) ?></td>
<td>$<?= number_format((float)$payment['balance'], 2) ?></td>
<td>
<?php
$method = strtolower((string)$payment['payment_method']);
if ($method === 'cash') {
echo '<span class="badge" style="background-color:#28a745;color:#fff;">Cash</span>';
} elseif ($method === 'check') {
echo '<span class="badge" style="background-color:#1ba2b9;color:#fff;">Check</span>';
} elseif ($method === 'card' || $method === 'debit/credit card') {
echo '<span class="badge" style="background-color:#007bff;color:#fff;">Card</span>';
} else {
echo '<span class="badge bg-secondary">' . esc($payment['payment_method']) . '</span>';
}
?>
</td>
<td><?= !empty($payment['check_number']) ? esc($payment['check_number']) : '-' ?></td>
<td><?= esc($payment['number_of_installments']) ?></td>
<td><?= esc($payment['status']) ?></td>
<td>
<?php if (!empty($payment['check_file'])): ?>
<!-- Trigger Modal -->
<a href="#" data-bs-toggle="modal" data-bs-target="#checkModal<?= (int)$payment['id'] ?>">View</a> /
<a href="<?= base_url('payment/file/' . (int) $payment['id'] . '/download') ?>">Download</a>
<!-- Modal -->
<div class="modal fade" id="checkModal<?= (int)$payment['id'] ?>" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Check Preview</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body text-center">
<iframe src="<?= base_url('payment/file/' . (int) $payment['id'] . '/inline') ?>"
width="100%" height="600px" style="border:none;"></iframe>
</div>
</div>
</div>
</div>
<?php else: ?>
-
<?php endif; ?>
</td>
<td>
<button class="btn btn-sm btn-secondary" data-bs-toggle="modal" data-bs-target="#editPaymentModal<?= (int)$payment['id'] ?>">Edit</button>
</td>
</tr>
<!-- Edit Payment Modal -->
<div class="modal fade" id="editPaymentModal<?= (int)$payment['id'] ?>" tabindex="-1" aria-labelledby="editPaymentModalLabel<?= (int)$payment['id'] ?>" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<form method="post" action="<?= base_url('payment/manual_pay_edit') ?>" enctype="multipart/form-data">
<?= csrf_field() ?>
<input type="hidden" name="payment_id" value="<?= (int)$payment['id'] ?>">
<input type="hidden" name="search_term" value="<?= esc($searchTermUsedInSearch ?? '') ?>">
<div class="modal-header">
<h5 class="modal-title" id="editPaymentModalLabel<?= (int)$payment['id'] ?>">Edit Payment ID <?= (int)$payment['id'] ?></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label>Paid Amount</label>
<input type="number" step="0.01" name="paid_amount" class="form-control" value="<?= esc($payment['paid_amount']) ?>" required>
</div>
<div class="mb-3">
<label>Payment Method</label>
<select name="payment_method" class="form-select edit-payment-method"
data-payment-id="<?= (int)$payment['id'] ?>"
onchange="toggleEditFields(<?= (int)$payment['id'] ?>)" required>
<option value="Cash" <?= ($payment['payment_method'] === 'Cash') ? 'selected' : '' ?>>Cash</option>
<option value="Check" <?= ($payment['payment_method'] === 'Check') ? 'selected' : '' ?>>Check</option>
<option value="Card" <?= ($payment['payment_method'] === 'Card') ? 'selected' : '' ?>>Debit/Credit Card</option>
</select>
</div>
<div class="mb-3 check-number-edit" id="check-number-edit-<?= (int)$payment['id'] ?>" style="display:none;">
<label>Check Number</label>
<input type="text" name="check_number" class="form-control"
value="<?= !empty($payment['check_number']) ? esc($payment['check_number']) : '' ?>">
</div>
<div class="mb-3" id="payment-file-edit-<?= (int)$payment['id'] ?>" style="display:none;">
<label>Replace Payment File (optional)</label>
<input type="file" name="payment_file" class="form-control" accept=".jpg,.jpeg,.png,.pdf">
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-success">Save Changes</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
</div>
</form>
</div>
</div>
</div>
<?php endforeach; ?>
</tbody>
</table>
<?php if (isset($pager)): ?>
<div class="d-flex justify-content-center">
<?= $pager->links() ?>
</div>
<?php endif; ?>
<!-- Invoices -->
<?php if (!empty($invoices)): ?>
<h5>Invoices</h5>
<table class="table table-bordered">
<thead>
<tr>
<th>Invoice #</th>
<th>Total</th>
<th>Paid</th>
<th>Balance</th>
<th>Status</th>
<th>Due Date</th>
</tr>
</thead>
<tbody>
<?php foreach ($invoices as $invoice): ?>
<?php
$dueDisp = '-';
if (!empty($invoice['due_date'])) {
$dueDisp = local_date($invoice['due_date'], 'm-d-Y');
}
?>
<tr>
<td><?= esc($invoice['invoice_number']) ?></td>
<td>$<?= number_format((float)$invoice['total_amount'], 2) ?></td>
<td>$<?= number_format((float)$invoice['paid_amount'], 2) ?></td>
<td>$<?= number_format((float)$invoice['balance'], 2) ?></td>
<td><?= esc($invoice['status']) ?></td>
<td><?= esc($dueDisp) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
<div class="text-end mb-3">
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addPaymentModal">Add Payment</button>
</div>
<?php elseif (isset($parent)): ?>
<p class="text-danger">Parent found but no payment records.</p>
<?php endif; ?>
</div>
<!-- Add Payment Modal -->
<div class="modal fade" id="addPaymentModal" tabindex="-1" aria-labelledby="addPaymentModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<!-- IMPORTANT: post to existing POST route payment/manual_pay -->
<form method="post" action="<?= base_url('payment/manual_pay') ?>" id="addPaymentForm" enctype="multipart/form-data">
<?= csrf_field() ?>
<input type="hidden" name="search_term" value="<?= esc($searchTermUsedInSearch ?? '') ?>">
<div class="modal-header">
<h5 class="modal-title" id="addPaymentModalLabel">Add Manual Payment</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<!-- Invoice select with per-invoice data for JS -->
<div class="mb-3">
<label for="invoice_id" class="form-label">Select Invoice</label>
<select
name="invoice_id"
id="invoice_id"
class="form-select"
required
onchange="onInvoiceChange(this)"
data-end-date="<?= esc($installmentEndYmd) ?>">
<?php if (!empty($invoices)): ?>
<?php foreach ($invoices as $invoice): ?>
<option
value="<?= (int)$invoice['id'] ?>"
data-balance="<?= esc($invoice['balance']) ?>">
Invoice <?= esc($invoice['invoice_number']) ?> |
Balance: $<?= number_format((float)$invoice['balance'], 2) ?> |
Paid: $<?= number_format((float)$invoice['paid_amount'], 2) ?>
</option>
<?php endforeach; ?>
<?php else: ?>
<option value="">No invoices available</option>
<?php endif; ?>
</select>
</div>
<!-- Method -->
<div class="mb-3">
<label for="payment_method" class="form-label">Payment Method</label>
<select name="payment_method" id="payment_method" class="form-select" required onchange="togglePaymentFieldsAdd()">
<option value="">Select Method</option>
<option value="cash">Cash</option>
<option value="check">Check</option>
<option value="card">Debit/Credit Card</option>
</select>
</div>
<!-- Check number (only for checks) -->
<div class="mb-3" id="check-number-field" style="display:none;">
<label for="check_number" class="form-label">Check Number</label>
<input type="text" name="check_number" id="check_number" class="form-control">
</div>
<!-- Receipt upload (check/card) -->
<div class="mb-3" id="payment-upload" style="display:none;">
<label for="payment_file" class="form-label">Upload File</label>
<input type="file" name="payment_file" id="payment_file" class="form-control" accept=".jpg,.jpeg,.png,.pdf">
</div>
<!-- Payment type -->
<div class="mb-3">
<label for="payment_type" class="form-label">Payment Type</label>
<select name="payment_type" id="payment_type" class="form-select" required onchange="toggleInstallmentFields()">
<option value="full">Full Payment</option>
<option value="installment">Installment Payment</option>
</select>
</div>
<!-- Installment section -->
<div id="installment-section" style="display:none;">
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="installment_count" class="form-label">Number of Installments</label>
<select name="installment_count" id="installment_count" class="form-select" onchange="recalcInstallments()"></select>
<div class="form-text">Maximum installments available: <span id="max-installments">0</span></div>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="installment_amount" class="form-label">Amount per Installment</label>
<input type="number" step="0.01" name="installment_amount" id="installment_amount" class="form-control" readonly>
</div>
</div>
</div>
<div class="text-muted small">
Choose the number of installments (up to the maximum available). The first payment is due today.
</div>
</div>
<!-- Amount -->
<div class="mb-3">
<label for="amount" class="form-label">Amount</label>
<input type="number" step="0.01" name="amount" id="amount" class="form-control" required>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-success">Submit Payment</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
</div>
</form>
</div>
</div>
</div>
<!-- End Payment Modal -->
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
// =========================
// Helpers
// =========================
function el(id) {
return document.getElementById(id);
}
function parseYMD(ymd) {
if (typeof ymd !== 'string') return null;
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(ymd.trim());
if (!m) return null;
const y = +m[1],
mo = +m[2],
d = +m[3];
const dt = new Date(y, mo - 1, d);
if (dt.getFullYear() !== y || dt.getMonth() !== mo - 1 || dt.getDate() !== d) return null;
return dt;
}
function todayYMD() {
const t = new Date();
const y = t.getFullYear();
const m = String(t.getMonth() + 1).padStart(2, '0');
const d = String(t.getDate()).padStart(2, '0');
return `${y}-${m}-${d}`;
}
// Months between TODAY and end date. Always >= 1.
function calculateInstallmentsFromToday(endDateStr) {
const start = parseYMD(todayYMD());
const end = parseYMD(endDateStr);
if (!start || !end) return 1;
if (end.getTime() < start.getTime()) return 1;
let months = (end.getFullYear() - start.getFullYear()) * 12 +
(end.getMonth() - start.getMonth());
if (end.getDate() > start.getDate()) months += 1;
return Math.max(1, months);
}
// =========================
// State
// =========================
let currentInvoiceBalance = 0;
let maxInstallmentNumber = 1;
// =========================
// Edit modal helpers
// =========================
function toggleEditFields(paymentId) {
const select = document.querySelector(`select[data-payment-id='${paymentId}']`);
const checkField = document.getElementById(`check-number-edit-${paymentId}`);
const fileField = document.getElementById(`payment-file-edit-${paymentId}`);
const input = checkField ? checkField.querySelector('input') : null;
if (!select || !checkField || !fileField || !input) return;
const v = (select.value || '').toLowerCase();
if (v === 'check') {
checkField.style.display = 'block';
fileField.style.display = 'block';
input.required = true;
} else if (v === 'card') {
checkField.style.display = 'none';
fileField.style.display = 'block';
input.required = false;
} else {
checkField.style.display = 'none';
fileField.style.display = 'none';
input.required = false;
}
}
// =========================
// Add Payment modal logic
// =========================
function onInvoiceChange(select) {
const opt = select.options[select.selectedIndex];
currentInvoiceBalance = parseFloat(opt?.dataset?.balance || '0') || 0;
// READ FROM THE SELECT (it has data-end-date)
const endDate = select?.dataset?.endDate || select.getAttribute('data-end-date') || '';
const computed = calculateInstallmentsFromToday(endDate);
maxInstallmentNumber = computed;
const maxSpan = el('max-installments');
if (maxSpan) maxSpan.textContent = String(maxInstallmentNumber);
populateInstallmentOptions();
toggleInstallmentFields();
recalcInstallments();
updateAmountField();
}
function populateInstallmentOptions() {
const countSelect = el('installment_count');
if (!countSelect) return;
countSelect.innerHTML = '';
for (let i = 1; i <= maxInstallmentNumber; i++) {
const option = document.createElement('option');
option.value = i;
option.textContent = i + (i === 1 ? ' installment' : ' installments');
countSelect.appendChild(option);
}
countSelect.value = String(maxInstallmentNumber);
}
function togglePaymentFieldsAdd() {
const method = (el('payment_method').value || '').toLowerCase();
const checkField = el('check-number-field');
const fileField = el('payment-upload');
const checkInput = el('check_number');
const ptype = el('payment_type');
const section = el('installment-section');
if (method === 'check') {
if (checkField) checkField.style.display = 'block';
if (fileField) fileField.style.display = 'block';
if (checkInput) checkInput.required = true;
if (ptype) ptype.disabled = false;
} else if (method === 'card') {
if (checkField) checkField.style.display = 'none';
if (fileField) fileField.style.display = 'block';
if (checkInput) checkInput.required = false;
if (ptype) {
ptype.value = 'full';
ptype.disabled = true;
}
if (section) section.style.display = 'none';
} else {
if (checkField) checkField.style.display = 'none';
if (fileField) fileField.style.display = 'none';
if (checkInput) checkInput.required = false;
if (ptype) ptype.disabled = false;
toggleInstallmentFields();
}
updateAmountField();
}
function toggleInstallmentFields() {
const method = (el('payment_method').value || '').toLowerCase();
const type = el('payment_type') ? el('payment_type').value : 'full';
const section = el('installment-section');
if (method === 'card') {
if (section) section.style.display = 'none';
return;
}
if (section) section.style.display = (type === 'installment') ? 'block' : 'none';
if (type === 'installment') {
populateInstallmentOptions();
}
}
function recalcInstallments() {
const method = (el('payment_method').value || '').toLowerCase();
const type = el('payment_type') ? el('payment_type').value : 'full';
const countSelect = el('installment_count');
const amtEl = el('installment_amount');
if (!countSelect || !amtEl) return;
if (type !== 'installment' || method === 'card') {
amtEl.value = '';
return;
}
const selected = parseInt(countSelect.value || '1', 10);
const raw = (currentInvoiceBalance > 0 ? (currentInvoiceBalance / selected) : 0);
const rounded = (Math.round(raw * 100) / 100).toFixed(2);
amtEl.value = rounded;
updateAmountField();
}
function updateAmountField() {
const method = (el('payment_method').value || '').toLowerCase();
const type = el('payment_type') ? el('payment_type').value : 'full';
const amountInput = el('amount');
if (!amountInput) return;
if (method === 'card' || type === 'full') {
amountInput.value = (currentInvoiceBalance || 0).toFixed(2);
} else {
const installmentAmount = el('installment_amount') ? (el('installment_amount').value || '0') : '0';
amountInput.value = installmentAmount;
}
}
// =========================
// Init
// =========================
document.addEventListener('DOMContentLoaded', function() {
// Initialize edit modals
document.querySelectorAll('.edit-payment-method').forEach(select => {
const id = select.getAttribute('data-payment-id');
toggleEditFields(id);
select.addEventListener('change', () => toggleEditFields(id));
});
// Wire Add Payment modal
const invoiceSelect = el('invoice_id');
if (invoiceSelect && invoiceSelect.options.length > 0) {
invoiceSelect.addEventListener('change', function() {
onInvoiceChange(this);
});
onInvoiceChange(invoiceSelect); // initial compute
}
const paymentTypeSel = el('payment_type');
if (paymentTypeSel) {
paymentTypeSel.addEventListener('change', function() {
toggleInstallmentFields();
recalcInstallments();
updateAmountField();
});
}
const paymentMethodSel = el('payment_method');
if (paymentMethodSel) {
paymentMethodSel.addEventListener('change', function() {
togglePaymentFieldsAdd();
recalcInstallments();
updateAmountField();
});
}
const installmentCountSel = el('installment_count');
if (installmentCountSel) {
installmentCountSel.addEventListener('change', function() {
recalcInstallments();
});
}
});
</script>
<?= $this->endSection() ?>