151 lines
6.7 KiB
PHP
Executable File
151 lines
6.7 KiB
PHP
Executable File
<?php
|
|
|
|
namespace App\Commands;
|
|
|
|
use CodeIgniter\CLI\BaseCommand;
|
|
use CodeIgniter\CLI\CLI;
|
|
use App\Models\ConfigurationModel;
|
|
use App\Models\InvoiceModel;
|
|
use App\Models\PaymentNotificationLogModel;
|
|
use App\Models\UserModel;
|
|
use App\Models\FamilyGuardianModel;
|
|
use App\Services\EmailService;
|
|
|
|
class SendTestPaymentNotification extends BaseCommand
|
|
{
|
|
protected $group = 'Payments';
|
|
protected $name = 'payments:send-test';
|
|
protected $description = 'Send a single test non-payment or installment reminder to a specific email.';
|
|
|
|
public function run(array $params)
|
|
{
|
|
$email = CLI::getOption('email');
|
|
$type = CLI::getOption('type') ?? 'no_payment';
|
|
if (!$email || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
|
CLI::write('Usage: php spark payments:send-test --email=addr@example.com [--type=no_payment|installment]', 'yellow');
|
|
return;
|
|
}
|
|
|
|
$config = new ConfigurationModel();
|
|
$user = new UserModel();
|
|
$invoice= new InvoiceModel();
|
|
$logs = new PaymentNotificationLogModel();
|
|
$fam = new FamilyGuardianModel();
|
|
$mailer = new EmailService();
|
|
|
|
$tzName = (string) (config('School')->attendance['timezone'] ?? 'UTC');
|
|
$tz = new \DateTimeZone($tzName);
|
|
$now = new \DateTime('now', $tz);
|
|
$year = (int)$now->format('Y');
|
|
$month= (int)$now->format('n');
|
|
$schoolYear = (string) ($config->getConfig('school_year') ?? $year);
|
|
|
|
$userRow = $user->where('email', $email)->first();
|
|
$parentId = (int)($userRow['id'] ?? 0);
|
|
|
|
$invRows = $parentId ? $invoice->getAllInvoicesByUserIds([$parentId], $schoolYear) : [];
|
|
$totalBalance = 0.0; $latestInvoiceId = null;
|
|
foreach ($invRows as $ir) {
|
|
$totalBalance += (float)($ir['balance'] ?? 0);
|
|
if ($latestInvoiceId === null) $latestInvoiceId = (int)($ir['id'] ?? 0);
|
|
}
|
|
|
|
// Secondary guardian if available
|
|
$ccEmail = null;
|
|
if ($parentId) {
|
|
$row = $fam->where('user_id', $parentId)->first();
|
|
if ($row && !empty($row['family_id'])) {
|
|
$others = $fam->where('family_id', (int)$row['family_id'])
|
|
->where('user_id !=', $parentId)
|
|
->where('receive_emails', 1)->findAll();
|
|
foreach ($others as $g) {
|
|
$ccEmail = $user->select('email')->find((int)$g['user_id'])['email'] ?? null;
|
|
if ($ccEmail && filter_var($ccEmail, FILTER_VALIDATE_EMAIL)) break;
|
|
$ccEmail = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Compose body (reuse layout)
|
|
$parentName = trim(($userRow['firstname'] ?? '') . ' ' . ($userRow['lastname'] ?? ''));
|
|
$monthYear = $now->format('F Y');
|
|
$subject = sprintf('Monthly Tuition Reminder — %s', $schoolYear);
|
|
$greeting = $parentName !== '' ? "Dear {$parentName}," : 'Dear Parent,';
|
|
$balanceFmt = '$' . number_format($totalBalance, 2);
|
|
$intro = ($type === 'no_payment')
|
|
? "We noticed there are outstanding tuition charges for {$schoolYear} but no payment has been recorded yet."
|
|
: "This is your monthly installment reminder for {$schoolYear}.";
|
|
|
|
$bodyHtml = <<<HTML
|
|
<div style="font-family:Arial,Helvetica,sans-serif; font-size:14px; color:#212529; line-height:1.5;">
|
|
<h2 style="margin:0 0 10px; font-size:18px;">Monthly Tuition Reminder (Test)</h2>
|
|
<p>{$greeting}</p>
|
|
<p>{$intro}</p>
|
|
<p><strong>Current Outstanding Balance:</strong> {$balanceFmt}</p>
|
|
<p>
|
|
As a friendly reminder, our tuition installments are due at the beginning of each month.
|
|
You can review your invoice and payment options by logging in to your parent portal.
|
|
</p>
|
|
<p>Thank you for your prompt attention.</p>
|
|
<p><em>Reminder Period:</em> {$monthYear}</p>
|
|
</div>
|
|
HTML;
|
|
// Compute remaining and max installments similar to Manual Payment UI
|
|
$installmentEndRaw = (string)$config->getConfig('installment_date');
|
|
$tz = new \DateTimeZone($tzName);
|
|
$today = new \DateTimeImmutable('today', $tz);
|
|
$end = null; try { if ($installmentEndRaw) { $end = new \DateTimeImmutable($installmentEndRaw, $tz); } } catch (\Throwable $e) { $end = null; }
|
|
$remMonths = 0;
|
|
if ($end) {
|
|
$y = (int)$end->format('Y') - (int)$today->format('Y');
|
|
$m = (int)$end->format('n') - (int)$today->format('n');
|
|
$remMonths = $y * 12 + $m;
|
|
if ((int)$end->format('j') > (int)$today->format('j')) $remMonths += 1;
|
|
if ($remMonths < 0) $remMonths = 0;
|
|
}
|
|
$remainingInst = max(1, $remMonths);
|
|
$maxInst = max(($remMonths ?: 0), ($totalBalance > 0 ? 2 : 0));
|
|
$instDueFmt = '$' . number_format($remainingInst > 0 ? ($totalBalance / $remainingInst) : 0, 2);
|
|
|
|
$bodyHtml .= "<ul>"
|
|
. "<li><strong>Remaining installments:</strong> {$remainingInst}</li>"
|
|
. "<li><strong>Suggested installment this month:</strong> {$instDueFmt}</li>"
|
|
. "<li><strong>Maximum installments available:</strong> {$maxInst}</li>"
|
|
. "</ul>";
|
|
|
|
$body = view('emails/_wrap_layout', [ 'title' => 'Monthly Tuition Reminder', 'body_html' => $bodyHtml ], ['saveData' => true]);
|
|
|
|
$ok = $mailer->send($email, $subject, $body, 'finance');
|
|
if ($ok && $ccEmail && strcasecmp($ccEmail, $email) !== 0) {
|
|
$mailer->send($ccEmail, $subject, $body, 'finance');
|
|
}
|
|
|
|
$existing = $logs->where('parent_id', $parentId)
|
|
->where('period_year', $year)
|
|
->where('period_month', $month)
|
|
->where('type', $type)
|
|
->first();
|
|
|
|
$payload = [
|
|
'parent_id' => $parentId,
|
|
'invoice_id' => $latestInvoiceId,
|
|
'school_year' => $schoolYear,
|
|
'period_year' => $year,
|
|
'period_month' => $month,
|
|
'type' => $type,
|
|
'to_email' => $email,
|
|
'cc_email' => $ccEmail,
|
|
'head_fa_notified' => 0,
|
|
'subject' => $subject,
|
|
'body' => $body,
|
|
'status' => $ok ? 'sent' : 'failed',
|
|
'error_message' => $ok ? null : 'Email send failed (see logs)',
|
|
'balance_snapshot' => $totalBalance,
|
|
'sent_at' => $now->format('Y-m-d H:i:s'),
|
|
];
|
|
if ($existing) $logs->update($existing['id'], $payload); else $logs->insert($payload);
|
|
|
|
CLI::write(($ok ? 'Sent' : 'Failed') . " test reminder to {$email}", $ok ? 'green' : 'red');
|
|
}
|
|
}
|