add controllers, servoices

This commit is contained in:
root
2026-03-09 02:52:13 -04:00
parent c8de5f7edc
commit d76c871cb7
501 changed files with 34439 additions and 21843 deletions
@@ -0,0 +1,59 @@
<?php
namespace App\Services\Communication;
use App\Models\FamilyStudent;
use Illuminate\Support\Facades\DB;
class CommunicationFamilyService
{
public function familiesForStudent(int $studentId): array
{
return FamilyStudent::getFamiliesForStudent($studentId);
}
public function guardiansForFamily(int $familyId): array
{
return DB::table('family_guardians as fg')
->join('users as u', 'u.id', '=', 'fg.user_id')
->where('fg.family_id', $familyId)
->select(
'u.id as user_id',
'u.firstname',
'u.lastname',
'u.email',
'fg.relation',
'fg.is_primary',
'fg.receive_emails',
'fg.receive_sms'
)
->get()
->map(fn ($row) => (array) $row)
->all();
}
public function guardianSalutation(int $familyId): string
{
$rows = DB::table('family_guardians as fg')
->join('users as u', 'u.id', '=', 'fg.user_id')
->where('fg.family_id', $familyId)
->where('fg.receive_emails', 1)
->orderByDesc('fg.is_primary')
->orderBy('u.lastname')
->orderBy('u.firstname')
->select('u.firstname', 'u.lastname')
->get()
->map(fn ($row) => (array) $row)
->all();
if (empty($rows)) {
return 'Parent/Guardian';
}
$names = array_map(static function ($row) {
return trim(($row['firstname'] ?? '') . ' ' . ($row['lastname'] ?? ''));
}, $rows);
return implode(' & ', $names);
}
}
@@ -0,0 +1,18 @@
<?php
namespace App\Services\Communication;
use App\Models\CommunicationLog;
use Illuminate\Support\Facades\Schema;
class CommunicationLogService
{
public function log(array $payload): void
{
if (!Schema::hasTable('communication_logs')) {
return;
}
CommunicationLog::query()->create($payload);
}
}
@@ -0,0 +1,84 @@
<?php
namespace App\Services\Communication;
class CommunicationPreviewService
{
private CommunicationTemplateService $templates;
private CommunicationStudentService $students;
private CommunicationFamilyService $families;
public function __construct(
CommunicationTemplateService $templates,
CommunicationStudentService $students,
CommunicationFamilyService $families
) {
$this->templates = $templates;
$this->students = $students;
$this->families = $families;
}
public function buildPreview(
string $templateKey,
int $studentId,
int $familyId,
array $vars,
string $teacherName
): array {
$template = $this->templates->findTemplateByKey($templateKey);
if (!$template) {
return [
'ok' => false,
'status' => 404,
'message' => 'Template not found',
];
}
$student = $this->students->getStudentBasic($studentId);
if (!$student) {
return [
'ok' => false,
'status' => 404,
'message' => 'Student not found',
];
}
$salutation = $this->families->guardianSalutation($familyId);
$autoVars = [
'student_fullname' => trim(($student['firstname'] ?? '') . ' ' . ($student['lastname'] ?? '')),
'student_grade' => $student['grade'] ?? '',
'parent_salutation' => $salutation,
'date' => $this->formatLocalDate('Y-m-d'),
'school_name' => 'Al Rahma Sunday School',
'teacher_name' => $teacherName !== '' ? $teacherName : 'Teacher',
];
$allVars = array_merge($autoVars, $vars);
$subject = $this->renderTwig($template['subject'], $allVars);
$body = $this->renderTwig($template['body'], $allVars);
return [
'ok' => true,
'subject' => $subject,
'html' => nl2br($body),
];
}
private function renderTwig(string $template, array $vars): string
{
return (string) preg_replace_callback('/\\{\\{\\s*([a-zA-Z0-9_\\.]+)\\s*\\}\\}/', function ($m) use ($vars) {
$key = $m[1];
return htmlspecialchars((string) ($vars[$key] ?? ''), ENT_QUOTES, 'UTF-8');
}, $template);
}
private function formatLocalDate(string $format): string
{
if (function_exists('local_date') && function_exists('utc_now')) {
return (string) local_date(utc_now(), $format);
}
return now()->format($format);
}
}
@@ -0,0 +1,97 @@
<?php
namespace App\Services\Communication;
use App\Services\EmailService;
class CommunicationSendService
{
private EmailService $mailer;
private CommunicationLogService $logService;
public function __construct(EmailService $mailer, CommunicationLogService $logService)
{
$this->mailer = $mailer;
$this->logService = $logService;
}
public function send(array $payload): array
{
$recipients = $this->normalizeEmails($payload['recipients'] ?? []);
$cc = $this->normalizeEmails($payload['cc'] ?? []);
$bcc = $this->normalizeEmails($payload['bcc'] ?? []);
if (empty($recipients)) {
$this->logService->log([
'student_id' => (int) $payload['student_id'],
'family_id' => (int) $payload['family_id'],
'student_name' => (string) $payload['student_name'],
'template_key' => (string) $payload['template_key'],
'subject' => (string) $payload['subject'],
'body' => (string) $payload['body'],
'recipients' => [],
'cc' => array_values($cc),
'bcc' => array_values($bcc),
'status' => 'failed',
'error_message' => 'No recipients provided',
'sent_by' => (int) ($payload['sent_by'] ?? 0),
'metadata' => null,
]);
return [
'ok' => false,
'error' => 'No recipients provided',
];
}
$sendOk = false;
$error = null;
try {
$sendOk = $this->mailer->send(
$recipients,
(string) $payload['subject'],
(string) $payload['body'],
'general',
$cc,
$bcc
);
} catch (\Throwable $t) {
$error = $t->getMessage();
$sendOk = false;
}
if (!$sendOk && $error === null) {
$error = 'Mailer returned false';
}
$this->logService->log([
'student_id' => (int) $payload['student_id'],
'family_id' => (int) $payload['family_id'],
'student_name' => (string) $payload['student_name'],
'template_key' => (string) $payload['template_key'],
'subject' => (string) $payload['subject'],
'body' => (string) $payload['body'],
'recipients' => array_values($recipients),
'cc' => array_values($cc),
'bcc' => array_values($bcc),
'status' => $sendOk ? 'sent' : 'failed',
'error_message' => $error,
'sent_by' => (int) ($payload['sent_by'] ?? 0),
'metadata' => null,
]);
return [
'ok' => $sendOk,
'error' => $error,
];
}
private function normalizeEmails(array $values): array
{
$out = array_values(array_unique(array_filter(array_map('strval', $values))));
return array_values(array_filter($out, static function ($email) {
return $email !== '';
}));
}
}
@@ -0,0 +1,53 @@
<?php
namespace App\Services\Communication;
use Illuminate\Support\Facades\DB;
class CommunicationStudentService
{
public function listStudents(): array
{
return DB::table('students')
->select('id', 'firstname', 'lastname', 'registration_grade')
->orderBy('lastname', 'asc')
->orderBy('firstname', 'asc')
->get()
->map(fn ($row) => (array) $row)
->all();
}
public function getStudentBasic(int $studentId): ?array
{
$row = DB::table('students as s')
->leftJoin('student_class as sc', 'sc.student_id', '=', 's.id')
->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'sc.class_section_id')
->select(
's.id',
's.firstname',
's.lastname',
's.registration_grade',
'cs.class_section_name as class_section_name'
)
->where('s.id', $studentId)
->orderBy('sc.id', 'desc')
->first();
if (!$row) {
return null;
}
$row = (array) $row;
$grade = $row['registration_grade'] ?? '';
if (!$grade && !empty($row['class_section_name'])) {
$grade = $row['class_section_name'];
}
return [
'id' => (int) $row['id'],
'firstname' => (string) ($row['firstname'] ?? ''),
'lastname' => (string) ($row['lastname'] ?? ''),
'grade' => (string) ($grade ?? ''),
];
}
}
@@ -0,0 +1,57 @@
<?php
namespace App\Services\Communication;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
class CommunicationTemplateService
{
public function listActiveTemplates(): array
{
[$keyColumn, $bodyColumn] = $this->resolveTemplateColumns();
return DB::table('email_templates')
->where('is_active', 1)
->orderBy($keyColumn, 'asc')
->get()
->map(function ($row) use ($keyColumn, $bodyColumn) {
$row = (array) $row;
return [
'template_key' => (string) ($row[$keyColumn] ?? ''),
'subject' => (string) ($row['subject'] ?? ''),
'body' => (string) ($row[$bodyColumn] ?? ''),
];
})
->all();
}
public function findTemplateByKey(string $key): ?array
{
[$keyColumn, $bodyColumn] = $this->resolveTemplateColumns();
$row = DB::table('email_templates')
->where($keyColumn, $key)
->where('is_active', 1)
->first();
if (!$row) {
return null;
}
$row = (array) $row;
return [
'template_key' => (string) ($row[$keyColumn] ?? ''),
'subject' => (string) ($row['subject'] ?? ''),
'body' => (string) ($row[$bodyColumn] ?? ''),
];
}
private function resolveTemplateColumns(): array
{
$keyColumn = Schema::hasColumn('email_templates', 'template_key') ? 'template_key' : 'code';
$bodyColumn = Schema::hasColumn('email_templates', 'body') ? 'body' : 'body_html';
return [$keyColumn, $bodyColumn];
}
}