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 = <<

Monthly Tuition Reminder (Test)

{$greeting}

{$intro}

Current Outstanding Balance: {$balanceFmt}

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.

Thank you for your prompt attention.

Reminder Period: {$monthYear}

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 .= ""; $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'); } }