215 lines
6.9 KiB
PHP
215 lines
6.9 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
use App\Models\BaseModel;
|
|
|
|
class WhatsappGroupMembership extends BaseModel
|
|
{
|
|
protected $table = 'whatsapp_group_memberships';
|
|
|
|
protected $fillable = [
|
|
'class_section_id',
|
|
'school_year',
|
|
'semester',
|
|
'subject_type',
|
|
'primary',
|
|
'second',
|
|
'subject_id',
|
|
'is_member',
|
|
'verified_by',
|
|
'verified_at',
|
|
];
|
|
public $timestamps = true; // created_at, updated_at
|
|
|
|
protected $casts = [
|
|
'class_section_id' => 'integer',
|
|
'subject_id' => 'integer',
|
|
'is_member' => 'boolean',
|
|
'verified_by' => 'integer',
|
|
'verified_at' => 'datetime',
|
|
'created_at' => 'datetime',
|
|
'updated_at' => 'datetime',
|
|
];
|
|
|
|
/* ============================================================
|
|
* Constants
|
|
* ============================================================
|
|
*/
|
|
public const TYPE_PRIMARY = 'primary';
|
|
public const TYPE_SECOND = 'second';
|
|
|
|
public static function normType(string $type): string
|
|
{
|
|
return strtolower(trim($type)) === self::TYPE_SECOND ? self::TYPE_SECOND : self::TYPE_PRIMARY;
|
|
}
|
|
|
|
/* ============================================================
|
|
* Scopes
|
|
* ============================================================
|
|
*/
|
|
|
|
public function scopeForSections(Builder $q, array $sectionIds): Builder
|
|
{
|
|
$ids = array_values(array_unique(array_filter(array_map('intval', $sectionIds))));
|
|
return empty($ids) ? $q->whereRaw('1=0') : $q->whereIn('class_section_id', $ids);
|
|
}
|
|
|
|
public function scopeForYear(Builder $q, string $year): Builder
|
|
{
|
|
return $q->where('school_year', trim($year));
|
|
}
|
|
|
|
public function scopeForSemester(Builder $q, string $sem): Builder
|
|
{
|
|
$sem = trim($sem);
|
|
return $sem === '' ? $q : $q->where('semester', $sem);
|
|
}
|
|
|
|
public function scopeForSubject(Builder $q, string $type, int $subjectId): Builder
|
|
{
|
|
return $q->where('subject_type', static::normType($type))
|
|
->where('subject_id', $subjectId);
|
|
}
|
|
|
|
/* ============================================================
|
|
* Methods (legacy-compatible behavior)
|
|
* ============================================================
|
|
*/
|
|
|
|
/**
|
|
* Fetch all membership rows for the given sections and term.
|
|
* Returns an associative index ["<sid>:<type>:<id>" => row(array)].
|
|
*/
|
|
public static function getBySectionsAndTerm(array $sectionIds, string $year, string $sem): array
|
|
{
|
|
$ids = array_values(array_unique(array_filter(array_map('intval', $sectionIds))));
|
|
if (empty($ids)) return [];
|
|
|
|
$year = trim($year);
|
|
$sem = trim($sem);
|
|
|
|
$q = static::query()
|
|
->forSections($ids)
|
|
->forYear($year);
|
|
|
|
if ($sem !== '') {
|
|
$q->where('semester', $sem);
|
|
}
|
|
|
|
// Prefer most recently updated membership when duplicates exist.
|
|
$rows = $q->orderByDesc('updated_at')
|
|
->orderByDesc('id')
|
|
->get();
|
|
|
|
$out = [];
|
|
foreach ($rows as $m) {
|
|
$k = sprintf('%d:%s:%d', (int)$m->class_section_id, (string)$m->subject_type, (int)$m->subject_id);
|
|
if (!isset($out[$k])) {
|
|
$out[$k] = $m->toArray();
|
|
}
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
|
|
/**
|
|
* Upsert membership row for a specific subject and class/term.
|
|
* Returns the row ID.
|
|
*
|
|
* Matches legacy logic:
|
|
* - Key is (class_section_id, school_year, subject_type, subject_id) plus semester if provided.
|
|
* - If semester is blank, we update most recent row for that key and keep its semester.
|
|
* - If verifiedBy provided, set verified_by + verified_at.
|
|
*/
|
|
public static function upsertMembership(
|
|
int $classSectionId,
|
|
string $year,
|
|
string $sem,
|
|
string $type,
|
|
int $subjectId,
|
|
bool $isMember,
|
|
?int $verifiedBy = null
|
|
): int {
|
|
$type = static::normType($type);
|
|
$year = trim($year);
|
|
$sem = trim($sem);
|
|
|
|
$payload = [
|
|
'class_section_id' => $classSectionId,
|
|
'school_year' => $year,
|
|
'semester' => $sem,
|
|
'subject_type' => $type,
|
|
'subject_id' => $subjectId,
|
|
'is_member' => $isMember ? 1 : 0,
|
|
];
|
|
|
|
if ($verifiedBy) {
|
|
$payload['verified_by'] = $verifiedBy;
|
|
$payload['verified_at'] = now();
|
|
}
|
|
|
|
// If semester is provided, we can do a clean updateOrCreate.
|
|
if ($sem !== '') {
|
|
$row = static::query()->updateOrCreate(
|
|
[
|
|
'class_section_id' => $classSectionId,
|
|
'school_year' => $year,
|
|
'semester' => $sem,
|
|
'subject_type' => $type,
|
|
'subject_id' => $subjectId,
|
|
],
|
|
$payload
|
|
);
|
|
return (int) $row->id;
|
|
}
|
|
|
|
// Semester blank => match legacy behavior: find most recent row for the key (without semester filter).
|
|
$existing = static::query()
|
|
->where('class_section_id', $classSectionId)
|
|
->where('school_year', $year)
|
|
->where('subject_type', $type)
|
|
->where('subject_id', $subjectId)
|
|
->orderByDesc('updated_at')
|
|
->orderByDesc('id')
|
|
->first();
|
|
|
|
if ($existing) {
|
|
// Keep semester from the existing row if present (legacy behavior).
|
|
if (!empty($existing->semester)) {
|
|
$payload['semester'] = (string) $existing->semester;
|
|
}
|
|
|
|
$existing->fill($payload);
|
|
$existing->save();
|
|
|
|
return (int) $existing->id;
|
|
}
|
|
|
|
// No existing row => create a new one (semester remains blank)
|
|
$created = static::query()->create($payload);
|
|
return (int) $created->id;
|
|
}
|
|
|
|
/* ============================================================
|
|
* Optional validation helper
|
|
* ============================================================
|
|
*/
|
|
|
|
public static function rules(bool $updating = false): array
|
|
{
|
|
$req = $updating ? 'sometimes' : 'required';
|
|
|
|
return [
|
|
'class_section_id' => [$req, 'integer', 'min:1'],
|
|
'school_year' => [$req, 'string', 'max:20'],
|
|
'semester' => ['nullable', 'string', 'max:20'],
|
|
'subject_type' => [$req, 'string', 'in:' . implode(',', [self::TYPE_PRIMARY, self::TYPE_SECOND])],
|
|
'subject_id' => [$req, 'integer', 'min:1'],
|
|
'is_member' => ['nullable', 'boolean'],
|
|
'verified_by' => ['nullable', 'integer'],
|
|
'verified_at' => ['nullable', 'date'],
|
|
];
|
|
}
|
|
} |