Files
alrahma_sunday_school_api/app/Services/Families/FamilyMutationService.php
T
2026-03-10 17:52:47 -04:00

341 lines
11 KiB
PHP

<?php
namespace App\Services\Families;
use App\Models\Family;
use App\Models\FamilyGuardian;
use App\Models\FamilyStudent;
use App\Models\Student;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schema;
class FamilyMutationService
{
public function bootstrapFamilies(): array
{
$this->ensureTablesExist(['families', 'family_students', 'family_guardians']);
return DB::transaction(function () {
$primaryParents = DB::table('students')
->select('parent_id')
->whereNotNull('parent_id')
->distinct()
->get()
->map(fn ($r) => (array) $r)
->all();
$created = 0;
$linkedStudents = 0;
$linkedGuardians = 0;
foreach ($primaryParents as $row) {
$primaryUserId = (int) ($row['parent_id'] ?? 0);
if ($primaryUserId <= 0) {
continue;
}
$familyId = $this->ensureFamilyForPrimaryParent($primaryUserId, $created);
$students = DB::table('students')
->select('id')
->where('parent_id', $primaryUserId)
->get()
->map(fn ($r) => (array) $r)
->all();
foreach ($students as $s) {
$linkedStudents += $this->attachStudentToFamily((int) ($s['id'] ?? 0), $familyId);
}
$linkedGuardians += $this->attachGuardianUser($familyId, $primaryUserId, 'primary', true, 1, 0);
}
return [
'families_created' => $created,
'students_linked' => $linkedStudents,
'guardians_linked' => $linkedGuardians,
];
});
}
public function attachSecondByUser(int $studentId, int $userId, string $relation = 'secondary'): array
{
if ($studentId <= 0 || $userId <= 0) {
return ['ok' => false, 'message' => 'student_id and user_id required'];
}
$familyId = $this->familyIdFromStudentPrimary($studentId);
if (!$familyId) {
return ['ok' => false, 'message' => 'No primary family found for this student'];
}
$rows = $this->attachGuardianUser($familyId, $userId, $relation, false, 1, 0);
return ['ok' => true, 'attached' => $rows, 'family_id' => $familyId];
}
public function attachSecondByEmail(int $studentId, string $email, ?string $firstname, ?string $lastname, string $relation = 'secondary'): array
{
if ($studentId <= 0 || $email === '') {
return ['ok' => false, 'message' => 'student_id and email required'];
}
$familyId = $this->familyIdFromStudentPrimary($studentId);
if (!$familyId) {
return ['ok' => false, 'message' => 'No primary family found for this student'];
}
return DB::transaction(function () use ($email, $firstname, $lastname, $relation, $familyId) {
$user = User::query()->where('email', $email)->first();
if (!$user) {
$user = User::query()->create([
'firstname' => (string) ($firstname ?? ''),
'lastname' => (string) ($lastname ?? ''),
'email' => $email,
'status' => 'Active',
'user_type' => 'secondary',
'cellphone' => '',
'address_street' => '',
'city' => '',
'state' => '',
'zip' => '',
'accept_school_policy' => 1,
'password' => bcrypt('temporary'),
'semester' => '',
]);
}
$rows = $this->attachGuardianUser($familyId, (int) $user->id, $relation, false, 1, 0);
return [
'ok' => true,
'attached' => $rows,
'family_id' => $familyId,
'user_id' => (int) $user->id,
];
});
}
public function setPrimaryHome(int $familyId, int $studentId, bool $isPrimaryHome): void
{
FamilyStudent::query()
->where('family_id', $familyId)
->where('student_id', $studentId)
->update(['is_primary_home' => $isPrimaryHome ? 1 : 0]);
}
public function setGuardianFlags(int $familyId, int $userId, array $flags): void
{
$data = [];
foreach (['receive_emails', 'is_primary', 'receive_sms', 'relation'] as $key) {
if (array_key_exists($key, $flags)) {
$value = $flags[$key];
$data[$key] = in_array($key, ['receive_emails', 'is_primary', 'receive_sms'], true)
? (int) $value
: (string) $value;
}
}
if (empty($data)) {
return;
}
FamilyGuardian::query()
->where('family_id', $familyId)
->where('user_id', $userId)
->update($data);
}
public function unlinkGuardian(int $familyId, int $userId): void
{
FamilyGuardian::query()
->where('family_id', $familyId)
->where('user_id', $userId)
->delete();
}
public function unlinkStudent(int $familyId, int $studentId): void
{
FamilyStudent::query()
->where('family_id', $familyId)
->where('student_id', $studentId)
->delete();
}
public function importSecondParentsFromLegacy(): array
{
$legacyTable = 'parents';
$this->ensureTablesExist(['families', 'family_students', 'family_guardians']);
if (!Schema::hasTable($legacyTable)) {
return ['ok' => false, 'message' => "Legacy table '{$legacyTable}' not found"];
}
$rows = DB::table($legacyTable)
->selectRaw('firstparent_id AS first_id, secondparent_id AS second_id, secondparent_email AS email, secondparent_firstname AS firstname, secondparent_lastname AS lastname')
->where(function ($q) {
$q->where('secondparent_id', '!=', 0)
->orWhere('secondparent_email', '!=', '');
})
->get()
->map(fn ($r) => (array) $r)
->all();
$createdUsers = 0;
$linked = 0;
$skipped = 0;
foreach ($rows as $r) {
$firstId = (int) ($r['first_id'] ?? 0);
$secondId = (int) ($r['second_id'] ?? 0);
$email = trim((string) ($r['email'] ?? ''));
if ($firstId <= 0 || ($secondId <= 0 && $email === '')) {
$skipped++;
continue;
}
$tmp = 0;
$familyId = $this->ensureFamilyForPrimaryParent($firstId, $tmp);
$userId = 0;
if ($secondId > 0) {
$userId = $secondId;
} else {
$user = User::query()->where('email', $email)->first();
if (!$user) {
$user = User::query()->create([
'firstname' => (string) ($r['firstname'] ?? ''),
'lastname' => (string) ($r['lastname'] ?? ''),
'email' => $email,
'status' => 'Active',
'user_type' => 'secondary',
'cellphone' => '',
'address_street' => '',
'city' => '',
'state' => '',
'zip' => '',
'accept_school_policy' => 1,
'password' => bcrypt('temporary'),
'semester' => '',
]);
$createdUsers++;
}
$userId = (int) $user->id;
}
$students = DB::table('students')
->select('id')
->where('parent_id', $firstId)
->get()
->map(fn ($r) => (array) $r)
->all();
foreach ($students as $sr) {
$sid = (int) ($sr['id'] ?? 0);
if ($sid > 0) {
$this->attachStudentToFamily($sid, $familyId);
}
}
$linked += $this->attachGuardianUser($familyId, $userId, 'guardian', false, 1, 0);
}
return [
'ok' => true,
'created_users' => $createdUsers,
'guardians_linked' => $linked,
'skipped' => $skipped,
'total_source' => count($rows),
];
}
private function ensureFamilyForPrimaryParent(int $primaryUserId, int &$createdCounter): int
{
$code = 'FAM-' . $primaryUserId;
$row = Family::query()->where('family_code', $code)->first();
if ($row) {
return (int) $row->id;
}
$family = Family::query()->create([
'family_code' => $code,
'household_name' => 'Family of User ' . $primaryUserId,
'is_active' => 1,
]);
$createdCounter++;
return (int) $family->id;
}
private function attachStudentToFamily(int $studentId, int $familyId): int
{
if ($studentId <= 0 || $familyId <= 0) {
return 0;
}
return DB::table('family_students')->insertOrIgnore([
'family_id' => $familyId,
'student_id' => $studentId,
'is_primary_home' => 1,
'created_at' => now(),
'updated_at' => now(),
]);
}
private function attachGuardianUser(
int $familyId,
int $userId,
string $relation = 'primary',
bool $isPrimary = false,
int $receiveEmails = 1,
int $receiveSms = 0
): int {
if ($familyId <= 0 || $userId <= 0) {
return 0;
}
return DB::table('family_guardians')->insertOrIgnore([
'family_id' => $familyId,
'user_id' => $userId,
'relation' => $relation,
'is_primary' => $isPrimary ? 1 : 0,
'receive_emails' => $receiveEmails,
'receive_sms' => $receiveSms,
'created_at' => now(),
'updated_at' => now(),
]);
}
private function familyIdFromStudentPrimary(int $studentId): ?int
{
$student = Student::query()->select('parent_id')->find($studentId);
if (!$student || empty($student->parent_id)) {
return null;
}
$code = 'FAM-' . (int) $student->parent_id;
$row = Family::query()->select('id')->where('family_code', $code)->first();
if ($row) {
return (int) $row->id;
}
$fallback = DB::table('family_students')
->select('family_id')
->where('student_id', $studentId)
->orderByDesc('is_primary_home')
->first();
return $fallback?->family_id ? (int) $fallback->family_id : null;
}
private function ensureTablesExist(array $tables): void
{
foreach ($tables as $table) {
if (!Schema::hasTable($table)) {
Log::warning('Family tables missing', ['table' => $table]);
throw new \RuntimeException("Missing required table '{$table}'. Run migrations.");
}
}
}
}