341 lines
11 KiB
PHP
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.");
|
|
}
|
|
}
|
|
}
|
|
}
|