add refund and event logic
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Events;
|
||||
|
||||
class EventCategoryService
|
||||
{
|
||||
public static function categories(): array
|
||||
{
|
||||
return [
|
||||
'workshops',
|
||||
'orientations',
|
||||
'field trips',
|
||||
'Ramadan programs',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Events;
|
||||
|
||||
use App\Models\EventCharges;
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class EventChargeQueryService
|
||||
{
|
||||
public function list(array $filters): array
|
||||
{
|
||||
$perPage = (int) ($filters['per_page'] ?? 25);
|
||||
$page = (int) ($filters['page'] ?? 1);
|
||||
|
||||
$query = EventCharges::query()
|
||||
->select([
|
||||
'event_charges.*',
|
||||
'users.firstname AS parent_firstname',
|
||||
'users.lastname AS parent_lastname',
|
||||
'students.firstname AS student_firstname',
|
||||
'students.lastname AS student_lastname',
|
||||
'events.event_name',
|
||||
])
|
||||
->leftJoin('users', 'users.id', '=', 'event_charges.parent_id')
|
||||
->leftJoin('students', 'students.id', '=', 'event_charges.student_id')
|
||||
->leftJoin('events', 'events.id', '=', 'event_charges.event_id');
|
||||
|
||||
if (!empty($filters['school_year'])) {
|
||||
$query->where('event_charges.school_year', (string) $filters['school_year']);
|
||||
}
|
||||
if (!empty($filters['semester'])) {
|
||||
$query->where('event_charges.semester', (string) $filters['semester']);
|
||||
}
|
||||
if (!empty($filters['parent_id'])) {
|
||||
$query->where('event_charges.parent_id', (int) $filters['parent_id']);
|
||||
}
|
||||
if (!empty($filters['event_id'])) {
|
||||
$query->where('event_charges.event_id', (int) $filters['event_id']);
|
||||
}
|
||||
|
||||
$paginator = $query->orderByDesc('event_charges.created_at')
|
||||
->paginate($perPage, ['*'], 'page', $page);
|
||||
|
||||
return [
|
||||
'charges' => $paginator,
|
||||
'pagination' => $this->paginationPayload($paginator),
|
||||
];
|
||||
}
|
||||
|
||||
private function paginationPayload(LengthAwarePaginator $paginator): array
|
||||
{
|
||||
return [
|
||||
'current_page' => $paginator->currentPage(),
|
||||
'per_page' => $paginator->perPage(),
|
||||
'total' => $paginator->total(),
|
||||
'total_pages' => $paginator->lastPage(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Events;
|
||||
|
||||
use App\Models\Enrollment;
|
||||
use App\Models\Event;
|
||||
use App\Models\EventCharges;
|
||||
use App\Services\Invoices\InvoiceGenerationService;
|
||||
|
||||
class EventChargeService
|
||||
{
|
||||
public function __construct(private InvoiceGenerationService $invoiceGeneration)
|
||||
{
|
||||
}
|
||||
|
||||
public function seedChargesForEvent(Event $event, string $schoolYear, string $semester, float $amount, int $actorId): array
|
||||
{
|
||||
$enrollments = Enrollment::query()
|
||||
->select('enrollments.student_id', 'students.parent_id')
|
||||
->join('students', 'students.id', '=', 'enrollments.student_id')
|
||||
->where('enrollments.school_year', $schoolYear)
|
||||
->whereIn('enrollments.enrollment_status', ['enrolled', 'payment pending'])
|
||||
->get()
|
||||
->map(fn ($row) => (array) $row)
|
||||
->all();
|
||||
|
||||
$parentIds = [];
|
||||
$created = 0;
|
||||
|
||||
foreach ($enrollments as $row) {
|
||||
$studentId = (int) ($row['student_id'] ?? 0);
|
||||
$parentId = (int) ($row['parent_id'] ?? 0);
|
||||
if ($studentId <= 0 || $parentId <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$exists = EventCharges::query()
|
||||
->where('event_id', $event->id)
|
||||
->where('student_id', $studentId)
|
||||
->where('school_year', $schoolYear)
|
||||
->where('semester', $semester)
|
||||
->first();
|
||||
|
||||
if ($exists) {
|
||||
continue;
|
||||
}
|
||||
|
||||
EventCharges::query()->create([
|
||||
'event_id' => (int) $event->id,
|
||||
'parent_id' => $parentId,
|
||||
'student_id' => $studentId,
|
||||
'participation' => 'yes',
|
||||
'charged' => $amount,
|
||||
'school_year' => $schoolYear,
|
||||
'semester' => $semester,
|
||||
'updated_by' => $actorId ?: null,
|
||||
'created_at' => utc_now(),
|
||||
'updated_at' => utc_now(),
|
||||
]);
|
||||
|
||||
$created++;
|
||||
$parentIds[] = $parentId;
|
||||
}
|
||||
|
||||
$parentIds = array_values(array_unique($parentIds));
|
||||
foreach ($parentIds as $parentId) {
|
||||
$this->invoiceGeneration->generateInvoice((int) $parentId, $schoolYear);
|
||||
}
|
||||
|
||||
return [
|
||||
'created' => $created,
|
||||
'parent_ids' => $parentIds,
|
||||
];
|
||||
}
|
||||
|
||||
public function updateParticipation(
|
||||
int $parentId,
|
||||
int $eventId,
|
||||
array $participations,
|
||||
string $schoolYear,
|
||||
string $semester,
|
||||
int $actorId
|
||||
): array {
|
||||
$event = Event::getEvent($eventId, $schoolYear);
|
||||
if (!$event) {
|
||||
return ['ok' => false, 'message' => 'Event not found.'];
|
||||
}
|
||||
|
||||
$created = 0;
|
||||
$updated = 0;
|
||||
$deleted = 0;
|
||||
|
||||
foreach ($participations as $studentId => $value) {
|
||||
$studentId = (int) $studentId;
|
||||
if ($studentId <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$existing = EventCharges::query()->where([
|
||||
'parent_id' => $parentId,
|
||||
'student_id' => $studentId,
|
||||
'event_id' => $eventId,
|
||||
'school_year' => $schoolYear,
|
||||
'semester' => $semester,
|
||||
])->first();
|
||||
|
||||
if ($value === 'no') {
|
||||
if ($existing) {
|
||||
$existing->delete();
|
||||
$deleted++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($existing) {
|
||||
$existing->update([
|
||||
'participation' => 'yes',
|
||||
'charged' => $event->amount,
|
||||
'updated_by' => $actorId,
|
||||
'updated_at' => utc_now(),
|
||||
]);
|
||||
$updated++;
|
||||
} else {
|
||||
EventCharges::query()->create([
|
||||
'parent_id' => $parentId,
|
||||
'student_id' => $studentId,
|
||||
'event_id' => $eventId,
|
||||
'participation' => 'yes',
|
||||
'charged' => $event->amount,
|
||||
'school_year' => $schoolYear,
|
||||
'semester' => $semester,
|
||||
'created_by' => $actorId,
|
||||
'updated_by' => $actorId,
|
||||
'created_at' => utc_now(),
|
||||
'updated_at' => utc_now(),
|
||||
]);
|
||||
$created++;
|
||||
}
|
||||
}
|
||||
|
||||
$this->invoiceGeneration->generateInvoice($parentId, $schoolYear);
|
||||
|
||||
return [
|
||||
'ok' => true,
|
||||
'created' => $created,
|
||||
'updated' => $updated,
|
||||
'deleted' => $deleted,
|
||||
];
|
||||
}
|
||||
|
||||
public function deleteChargesForEvent(int $eventId): array
|
||||
{
|
||||
$charges = EventCharges::query()
|
||||
->where('event_id', $eventId)
|
||||
->get()
|
||||
->map(fn ($row) => (array) $row)
|
||||
->all();
|
||||
|
||||
$parentIds = [];
|
||||
foreach ($charges as $charge) {
|
||||
EventCharges::query()->whereKey($charge['id'])->delete();
|
||||
$parentIds[] = (int) ($charge['parent_id'] ?? 0);
|
||||
}
|
||||
|
||||
return [
|
||||
'parent_ids' => array_values(array_unique(array_filter($parentIds))),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Events;
|
||||
|
||||
use App\Models\Event;
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
|
||||
class EventListService
|
||||
{
|
||||
public function list(array $filters): array
|
||||
{
|
||||
$perPage = (int) ($filters['per_page'] ?? 25);
|
||||
$page = (int) ($filters['page'] ?? 1);
|
||||
$sortBy = (string) ($filters['sort_by'] ?? 'created_at');
|
||||
$sortDir = strtolower((string) ($filters['sort_dir'] ?? 'desc')) === 'asc' ? 'asc' : 'desc';
|
||||
$activeOnly = $filters['active'] ?? null;
|
||||
|
||||
$allowedSorts = ['created_at', 'expiration_date', 'event_name'];
|
||||
if (!in_array($sortBy, $allowedSorts, true)) {
|
||||
$sortBy = 'created_at';
|
||||
}
|
||||
|
||||
$query = Event::query();
|
||||
|
||||
if (!empty($filters['school_year'])) {
|
||||
$query->where('school_year', (string) $filters['school_year']);
|
||||
}
|
||||
if (!empty($filters['semester'])) {
|
||||
$query->where('semester', (string) $filters['semester']);
|
||||
}
|
||||
if (!empty($filters['q'])) {
|
||||
$term = trim((string) $filters['q']);
|
||||
$query->where('event_name', 'like', "%{$term}%");
|
||||
}
|
||||
if ($activeOnly !== null) {
|
||||
$today = now()->toDateString();
|
||||
if (filter_var($activeOnly, FILTER_VALIDATE_BOOLEAN)) {
|
||||
$query->whereDate('expiration_date', '>=', $today);
|
||||
} else {
|
||||
$query->whereDate('expiration_date', '<', $today);
|
||||
}
|
||||
}
|
||||
|
||||
$paginator = $query
|
||||
->orderBy($sortBy, $sortDir)
|
||||
->paginate($perPage, ['*'], 'page', $page);
|
||||
|
||||
$activeCount = $this->activeCount($filters);
|
||||
|
||||
return [
|
||||
'events' => $paginator,
|
||||
'pagination' => $this->paginationPayload($paginator),
|
||||
'active_count' => $activeCount,
|
||||
];
|
||||
}
|
||||
|
||||
public function find(int $eventId, ?string $schoolYear = null): ?Event
|
||||
{
|
||||
$query = Event::query()->whereKey($eventId);
|
||||
if ($schoolYear !== null && $schoolYear !== '') {
|
||||
$query->where('school_year', $schoolYear);
|
||||
}
|
||||
|
||||
return $query->first();
|
||||
}
|
||||
|
||||
private function activeCount(array $filters): int
|
||||
{
|
||||
$query = Event::query()->whereDate('expiration_date', '>=', now()->toDateString());
|
||||
if (!empty($filters['school_year'])) {
|
||||
$query->where('school_year', (string) $filters['school_year']);
|
||||
}
|
||||
if (!empty($filters['semester'])) {
|
||||
$query->where('semester', (string) $filters['semester']);
|
||||
}
|
||||
|
||||
return (int) $query->count();
|
||||
}
|
||||
|
||||
private function paginationPayload(LengthAwarePaginator $paginator): array
|
||||
{
|
||||
return [
|
||||
'current_page' => $paginator->currentPage(),
|
||||
'per_page' => $paginator->perPage(),
|
||||
'total' => $paginator->total(),
|
||||
'total_pages' => $paginator->lastPage(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Events;
|
||||
|
||||
use App\Models\Event;
|
||||
use App\Services\Invoices\InvoiceGenerationService;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
||||
class EventManagementService
|
||||
{
|
||||
public function __construct(
|
||||
private EventChargeService $charges,
|
||||
private InvoiceGenerationService $invoiceGeneration
|
||||
) {
|
||||
}
|
||||
|
||||
public function create(array $payload, ?UploadedFile $flyer, int $actorId): array
|
||||
{
|
||||
return DB::transaction(function () use ($payload, $flyer, $actorId) {
|
||||
$flyerPath = $this->storeFlyer($flyer);
|
||||
|
||||
$event = Event::query()->create([
|
||||
'event_name' => $payload['event_name'],
|
||||
'event_category' => $payload['event_category'],
|
||||
'description' => $payload['description'] ?? null,
|
||||
'amount' => $payload['amount'],
|
||||
'flyer' => $flyerPath,
|
||||
'expiration_date' => $payload['expiration_date'],
|
||||
'semester' => $payload['semester'],
|
||||
'school_year' => $payload['school_year'],
|
||||
'created_by' => $actorId ?: null,
|
||||
'created_at' => utc_now(),
|
||||
'updated_at' => utc_now(),
|
||||
]);
|
||||
|
||||
$seed = $this->charges->seedChargesForEvent(
|
||||
$event,
|
||||
(string) $payload['school_year'],
|
||||
(string) $payload['semester'],
|
||||
(float) $payload['amount'],
|
||||
$actorId
|
||||
);
|
||||
|
||||
return [
|
||||
'ok' => true,
|
||||
'event' => $event,
|
||||
'charges_created' => $seed['created'] ?? 0,
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
public function update(int $eventId, array $payload, ?UploadedFile $flyer): array
|
||||
{
|
||||
$event = Event::query()->find($eventId);
|
||||
if (!$event) {
|
||||
return ['ok' => false, 'message' => 'Event not found.'];
|
||||
}
|
||||
|
||||
$flyerPath = $event->flyer;
|
||||
if ($flyer) {
|
||||
$flyerPath = $this->storeFlyer($flyer) ?: $flyerPath;
|
||||
}
|
||||
|
||||
$event->update([
|
||||
'event_name' => $payload['event_name'] ?? $event->event_name,
|
||||
'event_category' => $payload['event_category'] ?? $event->event_category,
|
||||
'description' => $payload['description'] ?? $event->description,
|
||||
'amount' => $payload['amount'] ?? $event->amount,
|
||||
'flyer' => $flyerPath,
|
||||
'expiration_date' => $payload['expiration_date'] ?? $event->expiration_date,
|
||||
'semester' => $payload['semester'] ?? $event->semester,
|
||||
'school_year' => $payload['school_year'] ?? $event->school_year,
|
||||
'updated_at' => utc_now(),
|
||||
]);
|
||||
|
||||
return ['ok' => true, 'event' => $event];
|
||||
}
|
||||
|
||||
public function delete(int $eventId, int $actorId): array
|
||||
{
|
||||
$event = Event::query()->find($eventId);
|
||||
if (!$event) {
|
||||
return ['ok' => false, 'message' => 'Event not found.'];
|
||||
}
|
||||
|
||||
return DB::transaction(function () use ($event) {
|
||||
$charges = $this->charges->deleteChargesForEvent((int) $event->id);
|
||||
$event->delete();
|
||||
|
||||
foreach ($charges['parent_ids'] ?? [] as $parentId) {
|
||||
$this->invoiceGeneration->generateInvoice((int) $parentId, (string) $event->school_year);
|
||||
}
|
||||
|
||||
return ['ok' => true];
|
||||
});
|
||||
}
|
||||
|
||||
private function storeFlyer(?UploadedFile $file): ?string
|
||||
{
|
||||
if (!$file || !$file->isValid()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$dir = public_path('uploads/event_flyers');
|
||||
if (!File::exists($dir)) {
|
||||
File::makeDirectory($dir, 0755, true);
|
||||
}
|
||||
|
||||
$name = $file->hashName();
|
||||
$file->move($dir, $name);
|
||||
|
||||
return 'event_flyers/' . $name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Events;
|
||||
|
||||
use App\Models\EventCharges;
|
||||
use App\Models\Student;
|
||||
|
||||
class EventStudentChargeService
|
||||
{
|
||||
public function listStudentsWithCharges(int $parentId, string $schoolYear, string $semester): array
|
||||
{
|
||||
$students = Student::query()
|
||||
->where('parent_id', $parentId)
|
||||
->get()
|
||||
->map(fn ($row) => (array) $row)
|
||||
->all();
|
||||
|
||||
$chargedIds = EventCharges::query()
|
||||
->where('parent_id', $parentId)
|
||||
->where('school_year', $schoolYear)
|
||||
->where('semester', $semester)
|
||||
->distinct()
|
||||
->pluck('student_id')
|
||||
->map(fn ($id) => (int) $id)
|
||||
->all();
|
||||
|
||||
$chargedMap = array_flip($chargedIds);
|
||||
|
||||
$data = [];
|
||||
foreach ($students as $student) {
|
||||
$data[] = [
|
||||
'id' => (int) ($student['id'] ?? 0),
|
||||
'name' => trim(($student['firstname'] ?? '') . ' ' . ($student['lastname'] ?? '')),
|
||||
'charged' => isset($chargedMap[(int) ($student['id'] ?? 0)]),
|
||||
];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user