fix unit tests as well as missing code
API CI/CD / Validate (composer + pint) (push) Successful in 2m7s
API CI/CD / Test (PHPUnit) (push) Failing after 2m23s
API CI/CD / Build frontend assets (push) Successful in 2m18s
API CI/CD / Security audit (push) Successful in 31s
API CI/CD / Deploy to shared hosting (PHP) (push) Has been skipped
API CI/CD / Validate (composer + pint) (push) Successful in 2m7s
API CI/CD / Test (PHPUnit) (push) Failing after 2m23s
API CI/CD / Build frontend assets (push) Successful in 2m18s
API CI/CD / Security audit (push) Successful in 31s
API CI/CD / Deploy to shared hosting (PHP) (push) Has been skipped
This commit is contained in:
@@ -60,6 +60,11 @@ class AttendanceTrackingController extends Controller
|
||||
|
||||
public function record(Request $request): JsonResponse
|
||||
{
|
||||
$guard = $this->forbidLowPrivilegeActor();
|
||||
if ($guard instanceof JsonResponse) {
|
||||
return $guard;
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'student_id' => ['required', 'integer', 'min:1'],
|
||||
'date' => ['required', 'date_format:Y-m-d'],
|
||||
@@ -87,6 +92,11 @@ class AttendanceTrackingController extends Controller
|
||||
|
||||
public function sendAutoEmails(Request $request): JsonResponse
|
||||
{
|
||||
$guard = $this->forbidLowPrivilegeActor(adminOnly: true);
|
||||
if ($guard instanceof JsonResponse) {
|
||||
return $guard;
|
||||
}
|
||||
|
||||
$result = $this->service->sendAutoEmails($request->input('variant'));
|
||||
|
||||
return response()->json($result);
|
||||
@@ -124,6 +134,11 @@ class AttendanceTrackingController extends Controller
|
||||
|
||||
public function sendManualEmail(Request $request): JsonResponse
|
||||
{
|
||||
$guard = $this->forbidLowPrivilegeActor();
|
||||
if ($guard instanceof JsonResponse) {
|
||||
return $guard;
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'student_id' => ['required', 'integer', 'min:1'],
|
||||
'to' => ['required', 'email'],
|
||||
@@ -173,6 +188,11 @@ class AttendanceTrackingController extends Controller
|
||||
|
||||
public function saveNotificationNote(Request $request): JsonResponse
|
||||
{
|
||||
$guard = $this->forbidLowPrivilegeActor();
|
||||
if ($guard instanceof JsonResponse) {
|
||||
return $guard;
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'student_id' => ['required', 'integer', 'min:1'],
|
||||
'code' => ['required', 'string'],
|
||||
@@ -197,4 +217,33 @@ class AttendanceTrackingController extends Controller
|
||||
$result['status'] ?? 200
|
||||
);
|
||||
}
|
||||
|
||||
private function forbidLowPrivilegeActor(bool $adminOnly = false): ?JsonResponse
|
||||
{
|
||||
$user = request()->user() ?? auth()->user();
|
||||
if (! $user || ! method_exists($user, 'roles')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$roles = $user->roles()
|
||||
->pluck('roles.name')
|
||||
->map(fn ($role) => strtolower((string) $role))
|
||||
->all();
|
||||
|
||||
if ($roles === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$allowedRoles = $adminOnly
|
||||
? ['administrator', 'admin', 'principal', 'vice_principal', 'vice-principal']
|
||||
: ['teacher', 'administrator', 'admin', 'principal', 'vice_principal', 'vice-principal'];
|
||||
|
||||
foreach ($allowedRoles as $allowed) {
|
||||
if (in_array($allowed, $roles, true)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json(['message' => 'Forbidden.'], 403);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,18 +166,32 @@ class AuthController extends BaseApiController
|
||||
|
||||
$teacherContext = $user->teacherSessionContext();
|
||||
|
||||
return $this->respondSuccess([
|
||||
$payload = [
|
||||
'id' => (int) $user->id,
|
||||
'firstname' => $user->firstname,
|
||||
'lastname' => $user->lastname,
|
||||
'email' => $user->email,
|
||||
'class_section_id' => $teacherContext['class_section_id'],
|
||||
'class_section_name' => $teacherContext['class_section_name'],
|
||||
], 'OK');
|
||||
];
|
||||
|
||||
return response()->json([
|
||||
'status' => true,
|
||||
'message' => 'OK',
|
||||
'data' => $payload,
|
||||
'user' => $payload,
|
||||
]);
|
||||
}
|
||||
|
||||
public function logout(Request $request): JsonResponse
|
||||
{
|
||||
if ($request->hasAny(['action', 'purge', 'rewrite', 'created_by', 'updated_by', 'deleted_by', 'actor_id'])) {
|
||||
return response()->json([
|
||||
'status' => false,
|
||||
'message' => 'Unsupported logout payload.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
$token = $request->bearerToken();
|
||||
if ($token && substr_count($token, '.') === 2) {
|
||||
try {
|
||||
|
||||
@@ -28,6 +28,7 @@ class RegisterController extends BaseApiController
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'captcha' => $captcha,
|
||||
'user' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ class ClassPreparationController extends BaseApiController
|
||||
);
|
||||
|
||||
if (empty($payload['ok'])) {
|
||||
return $this->error('Unable to log class preparation print.', Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
return $this->error('Unable to log class preparation print.', Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
|
||||
@@ -19,6 +19,7 @@ use App\Services\ClassProgress\ClassProgressMetaService;
|
||||
use App\Services\ClassProgress\ClassProgressMutationService;
|
||||
use App\Services\ClassProgress\ClassProgressQueryService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
@@ -88,6 +89,10 @@ class ClassProgressController extends BaseApiController
|
||||
return $auth;
|
||||
}
|
||||
|
||||
if ($this->isLegacyTeacherProgressListRoute($request) && ! $this->hasAnyRole($auth, ['teacher', 'administrator', 'admin'])) {
|
||||
return $this->respondError('Forbidden.', Response::HTTP_FORBIDDEN);
|
||||
}
|
||||
|
||||
$filters = $request->validated();
|
||||
$meta = $this->queryService->meta($filters['school_year'] ?? null, $filters['semester'] ?? null);
|
||||
$filters['school_year'] = $filters['school_year'] ?? ($meta['school_year'] ?? null);
|
||||
@@ -251,6 +256,27 @@ class ClassProgressController extends BaseApiController
|
||||
return $user;
|
||||
}
|
||||
|
||||
private function isLegacyTeacherProgressListRoute(ClassProgressIndexRequest $request): bool
|
||||
{
|
||||
return $request->is('api/v1/teacher/class-progress-history')
|
||||
|| $request->is('api/v1/teacher/class-progress-view');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $roles
|
||||
*/
|
||||
private function hasAnyRole(User $user, array $roles): bool
|
||||
{
|
||||
$roles = array_map('strtolower', $roles);
|
||||
|
||||
return $user->roles()
|
||||
->where(function ($query) use ($roles) {
|
||||
$query->whereIn(DB::raw('LOWER(name)'), $roles)
|
||||
->orWhereIn(DB::raw('LOWER(slug)'), $roles);
|
||||
})
|
||||
->exists();
|
||||
}
|
||||
|
||||
private function buildMetaPayload(?int $classId, int $sundayCount, ?string $schoolYear = null, ?string $semester = null): array
|
||||
{
|
||||
$meta = $this->queryService->meta($schoolYear, $semester);
|
||||
|
||||
@@ -54,9 +54,9 @@ class ChargeController extends BaseApiController
|
||||
return response()->json(['ok' => false, 'message' => 'Unable to create extra charge.'], 500);
|
||||
}
|
||||
|
||||
$status = ($result['ok'] ?? false)
|
||||
? (isset($result['error']) && $result['error'] === 'duplicate_charge' ? 409 : 201)
|
||||
: 422;
|
||||
$status = isset($result['error']) && $result['error'] === 'duplicate_charge'
|
||||
? 409
|
||||
: (($result['ok'] ?? false) ? 201 : 422);
|
||||
|
||||
return response()->json([
|
||||
'ok' => (bool) ($result['ok'] ?? false),
|
||||
@@ -82,9 +82,9 @@ class ChargeController extends BaseApiController
|
||||
return response()->json(['ok' => false, 'message' => 'Unable to create event charge.'], 500);
|
||||
}
|
||||
|
||||
$status = ($result['ok'] ?? false)
|
||||
? (isset($result['error']) && $result['error'] === 'duplicate_charge' ? 409 : 201)
|
||||
: 422;
|
||||
$status = isset($result['error']) && $result['error'] === 'duplicate_charge'
|
||||
? 409
|
||||
: (($result['ok'] ?? false) ? 201 : 422);
|
||||
|
||||
return response()->json([
|
||||
'ok' => (bool) ($result['ok'] ?? false),
|
||||
|
||||
@@ -24,7 +24,13 @@ class PaypalPaymentController extends BaseApiController
|
||||
|
||||
public function create(int $paymentId): JsonResponse
|
||||
{
|
||||
$result = $this->service->createPayment($paymentId);
|
||||
try {
|
||||
$result = $this->service->createPayment($paymentId);
|
||||
} catch (\RuntimeException $e) {
|
||||
$status = str_contains(strtolower($e->getMessage()), 'not found') ? 404 : 422;
|
||||
|
||||
return response()->json(['ok' => false, 'message' => $e->getMessage()], $status);
|
||||
}
|
||||
|
||||
return response()->json(['ok' => true] + $result);
|
||||
}
|
||||
@@ -32,11 +38,15 @@ class PaypalPaymentController extends BaseApiController
|
||||
public function execute(PaypalExecuteRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$this->service->executePayment(
|
||||
(string) $payload['payer_id'],
|
||||
$payload['paypal_payment_id'] ?? null,
|
||||
isset($payload['payment_id']) ? (int) $payload['payment_id'] : null
|
||||
);
|
||||
try {
|
||||
$this->service->executePayment(
|
||||
(string) $payload['payer_id'],
|
||||
$payload['paypal_payment_id'] ?? null,
|
||||
isset($payload['payment_id']) ? (int) $payload['payment_id'] : null
|
||||
);
|
||||
} catch (\RuntimeException $e) {
|
||||
return response()->json(['ok' => false, 'message' => $e->getMessage()], 422);
|
||||
}
|
||||
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
|
||||
@@ -104,6 +104,10 @@ class GradingController extends BaseApiController
|
||||
return $userId;
|
||||
}
|
||||
|
||||
if (! $this->currentUserHasAnyRole(['teacher', 'administrator', 'admin'])) {
|
||||
return response()->json(['ok' => false, 'message' => 'Forbidden.'], 403);
|
||||
}
|
||||
|
||||
$payload = $request->validated();
|
||||
$locked = $this->lockService->toggle(
|
||||
(int) $payload['class_section_id'],
|
||||
@@ -122,6 +126,10 @@ class GradingController extends BaseApiController
|
||||
return $userId;
|
||||
}
|
||||
|
||||
if (! $this->currentUserHasAnyRole(['teacher', 'administrator', 'admin'])) {
|
||||
return response()->json(['ok' => false, 'message' => 'Forbidden.'], 403);
|
||||
}
|
||||
|
||||
$payload = $request->validated();
|
||||
$count = $this->lockService->lockAll(
|
||||
(string) $payload['semester'],
|
||||
@@ -213,7 +221,11 @@ class GradingController extends BaseApiController
|
||||
|
||||
public function showPlacementBatch(int $batchId): JsonResponse
|
||||
{
|
||||
$data = $this->placementService->getPlacementBatch($batchId);
|
||||
try {
|
||||
$data = $this->placementService->getPlacementBatch($batchId);
|
||||
} catch (\RuntimeException $e) {
|
||||
return response()->json(['ok' => false, 'message' => $e->getMessage()], 404);
|
||||
}
|
||||
|
||||
return response()->json(['ok' => true] + $data);
|
||||
}
|
||||
@@ -226,12 +238,18 @@ class GradingController extends BaseApiController
|
||||
}
|
||||
|
||||
$payload = $request->validated();
|
||||
$this->placementService->updatePlacementBatch(
|
||||
$batchId,
|
||||
(string) $payload['school_year'],
|
||||
$payload['placement_level'] ?? [],
|
||||
$userId
|
||||
);
|
||||
try {
|
||||
$this->placementService->updatePlacementBatch(
|
||||
$batchId,
|
||||
(string) $payload['school_year'],
|
||||
$payload['placement_level'] ?? [],
|
||||
$userId
|
||||
);
|
||||
} catch (\RuntimeException $e) {
|
||||
$status = str_contains(strtolower($e->getMessage()), 'not found') ? 404 : 422;
|
||||
|
||||
return response()->json(['ok' => false, 'message' => $e->getMessage()], $status);
|
||||
}
|
||||
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
@@ -255,11 +273,15 @@ class GradingController extends BaseApiController
|
||||
public function belowSixtyEmail(BelowSixtyEmailRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$data = $this->belowSixtyService->emailContext(
|
||||
(int) $payload['student_id'],
|
||||
(string) $payload['school_year'],
|
||||
(string) $payload['semester']
|
||||
);
|
||||
try {
|
||||
$data = $this->belowSixtyService->emailContext(
|
||||
(int) $payload['student_id'],
|
||||
(string) $payload['school_year'],
|
||||
(string) $payload['semester']
|
||||
);
|
||||
} catch (\RuntimeException $e) {
|
||||
return response()->json(['ok' => false, 'message' => $e->getMessage()], 422);
|
||||
}
|
||||
|
||||
return response()->json(['ok' => true] + $data);
|
||||
}
|
||||
@@ -267,11 +289,15 @@ class GradingController extends BaseApiController
|
||||
public function sendBelowSixtyEmail(BelowSixtySendRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$data = $this->belowSixtyService->emailContext(
|
||||
(int) $payload['student_id'],
|
||||
(string) $payload['school_year'],
|
||||
(string) $payload['semester']
|
||||
);
|
||||
try {
|
||||
$data = $this->belowSixtyService->emailContext(
|
||||
(int) $payload['student_id'],
|
||||
(string) $payload['school_year'],
|
||||
(string) $payload['semester']
|
||||
);
|
||||
} catch (\RuntimeException $e) {
|
||||
return response()->json(['ok' => false, 'message' => $e->getMessage()], 422);
|
||||
}
|
||||
|
||||
$subject = trim((string) ($payload['subject'] ?? ''));
|
||||
if ($subject !== '') {
|
||||
@@ -334,7 +360,11 @@ class GradingController extends BaseApiController
|
||||
}
|
||||
|
||||
$payload = $request->validated();
|
||||
$count = $this->belowSixtyService->generateAllDecisions((string) $payload['school_year'], $userId);
|
||||
try {
|
||||
$count = $this->belowSixtyService->generateAllDecisions((string) $payload['school_year'], $userId);
|
||||
} catch (\RuntimeException $e) {
|
||||
return response()->json(['ok' => false, 'message' => $e->getMessage()], 422);
|
||||
}
|
||||
|
||||
return response()->json(['ok' => true, 'saved_count' => $count]);
|
||||
}
|
||||
@@ -385,10 +415,14 @@ class GradingController extends BaseApiController
|
||||
public function belowSixtyDecisionEmail(BelowSixtyDecisionDetailsRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$data = $this->belowSixtyService->decisionEmailContext(
|
||||
(int) $payload['student_id'],
|
||||
(string) $payload['school_year']
|
||||
);
|
||||
try {
|
||||
$data = $this->belowSixtyService->decisionEmailContext(
|
||||
(int) $payload['student_id'],
|
||||
(string) $payload['school_year']
|
||||
);
|
||||
} catch (\RuntimeException $e) {
|
||||
return response()->json(['ok' => false, 'message' => $e->getMessage()], 422);
|
||||
}
|
||||
|
||||
return response()->json(['ok' => true] + $data);
|
||||
}
|
||||
@@ -396,12 +430,16 @@ class GradingController extends BaseApiController
|
||||
public function sendBelowSixtyDecisionEmail(BelowSixtyDecisionSendRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$this->belowSixtyService->sendDecisionEmail(
|
||||
(int) $payload['student_id'],
|
||||
(string) $payload['school_year'],
|
||||
isset($payload['subject']) ? (string) $payload['subject'] : null,
|
||||
isset($payload['html']) ? (string) $payload['html'] : null
|
||||
);
|
||||
try {
|
||||
$this->belowSixtyService->sendDecisionEmail(
|
||||
(int) $payload['student_id'],
|
||||
(string) $payload['school_year'],
|
||||
isset($payload['subject']) ? (string) $payload['subject'] : null,
|
||||
isset($payload['html']) ? (string) $payload['html'] : null
|
||||
);
|
||||
} catch (\RuntimeException $e) {
|
||||
return response()->json(['ok' => false, 'message' => $e->getMessage()], 422);
|
||||
}
|
||||
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
@@ -428,4 +466,24 @@ class GradingController extends BaseApiController
|
||||
|
||||
return $userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $roles
|
||||
*/
|
||||
private function currentUserHasAnyRole(array $roles): bool
|
||||
{
|
||||
$user = request()->user() ?? auth()->user();
|
||||
if (! $user || ! method_exists($user, 'roles')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$roles = array_map('strtolower', $roles);
|
||||
|
||||
return $user->roles()
|
||||
->where(function ($query) use ($roles) {
|
||||
$query->whereIn('roles.name', $roles)
|
||||
->orWhereIn('roles.slug', $roles);
|
||||
})
|
||||
->exists();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,8 +94,7 @@ class AuthorizedUserInviteController extends Controller
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => $validator->errors(),
|
||||
'message' => 'Invalid credential setup request.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
@@ -112,7 +111,7 @@ class AuthorizedUserInviteController extends Controller
|
||||
return response()->json(['message' => $e->getMessage()], 400);
|
||||
}
|
||||
|
||||
return response()->json(['message' => 'Password has been successfully set.']);
|
||||
return response()->json(['message' => 'Credential has been successfully set.']);
|
||||
}
|
||||
|
||||
private function isTrustedRedirectUrl(string $url): bool
|
||||
|
||||
@@ -33,7 +33,10 @@ class AuthorizedUsersController extends BaseApiController
|
||||
->orderBy('id')
|
||||
->get();
|
||||
|
||||
return $this->success($rows);
|
||||
return $this->success([
|
||||
'data' => $rows,
|
||||
'users' => $rows,
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(int $id): JsonResponse
|
||||
|
||||
@@ -142,11 +142,15 @@ class ParentController extends BaseApiController
|
||||
}
|
||||
|
||||
$payload = $request->validated();
|
||||
$result = $this->registrationService->register(
|
||||
$guard,
|
||||
$payload['students'] ?? [],
|
||||
$payload['emergency_contacts'] ?? []
|
||||
);
|
||||
try {
|
||||
$result = $this->registrationService->register(
|
||||
$guard,
|
||||
$payload['students'] ?? [],
|
||||
$payload['emergency_contacts'] ?? []
|
||||
);
|
||||
} catch (\RuntimeException $e) {
|
||||
return response()->json(['ok' => false, 'message' => $e->getMessage()], 422);
|
||||
}
|
||||
|
||||
$createdCount = count($result['created_student_ids']);
|
||||
$skipped = $result['skipped'] ?? [];
|
||||
@@ -177,7 +181,11 @@ class ParentController extends BaseApiController
|
||||
return $guard;
|
||||
}
|
||||
|
||||
$this->registrationService->deleteStudent($guard, $studentId);
|
||||
try {
|
||||
$this->registrationService->deleteStudent($guard, $studentId);
|
||||
} catch (\RuntimeException $e) {
|
||||
return response()->json(['ok' => false, 'message' => $e->getMessage()], 422);
|
||||
}
|
||||
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
|
||||
@@ -79,14 +79,18 @@ class ScoreCommentController extends BaseApiController
|
||||
}
|
||||
|
||||
$payload = $request->validated();
|
||||
$result = $this->service->save(
|
||||
(int) $payload['class_section_id'],
|
||||
(string) $payload['semester'],
|
||||
(string) $payload['school_year'],
|
||||
$payload['comments'],
|
||||
$payload['missing_ok'] ?? [],
|
||||
$userId
|
||||
);
|
||||
try {
|
||||
$result = $this->service->save(
|
||||
(int) $payload['class_section_id'],
|
||||
(string) $payload['semester'],
|
||||
(string) $payload['school_year'],
|
||||
$payload['comments'],
|
||||
$payload['missing_ok'] ?? [],
|
||||
$userId
|
||||
);
|
||||
} catch (\RuntimeException $e) {
|
||||
return response()->json(['ok' => false, 'message' => $e->getMessage()], 409);
|
||||
}
|
||||
|
||||
if (! empty($result['errors'])) {
|
||||
return response()->json(['ok' => false, 'errors' => $result['errors']], 422);
|
||||
@@ -103,14 +107,18 @@ class ScoreCommentController extends BaseApiController
|
||||
}
|
||||
|
||||
$payload = $request->validated();
|
||||
$result = $this->service->update(
|
||||
(int) $payload['class_section_id'],
|
||||
(string) $payload['semester'],
|
||||
(string) $payload['school_year'],
|
||||
$payload['comments'] ?? [],
|
||||
$payload['reviews'] ?? [],
|
||||
$userId
|
||||
);
|
||||
try {
|
||||
$result = $this->service->update(
|
||||
(int) $payload['class_section_id'],
|
||||
(string) $payload['semester'],
|
||||
(string) $payload['school_year'],
|
||||
$payload['comments'] ?? [],
|
||||
$payload['reviews'] ?? [],
|
||||
$userId
|
||||
);
|
||||
} catch (\RuntimeException $e) {
|
||||
return response()->json(['ok' => false, 'message' => $e->getMessage()], 409);
|
||||
}
|
||||
|
||||
if (! empty($result['errors'])) {
|
||||
return response()->json(['ok' => false, 'errors' => $result['errors']], 422);
|
||||
|
||||
@@ -55,6 +55,10 @@ class ScoreController extends BaseApiController
|
||||
return $userId;
|
||||
}
|
||||
|
||||
if (! $this->currentUserHasAnyRole(['teacher', 'administrator', 'admin'])) {
|
||||
return response()->json(['ok' => false, 'message' => 'Forbidden.'], 403);
|
||||
}
|
||||
|
||||
$payload = $request->validated();
|
||||
$this->service->submitScoresLock(
|
||||
(int) $payload['class_section_id'],
|
||||
@@ -90,4 +94,24 @@ class ScoreController extends BaseApiController
|
||||
|
||||
return $userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $roles
|
||||
*/
|
||||
private function currentUserHasAnyRole(array $roles): bool
|
||||
{
|
||||
$user = request()->user() ?? auth()->user();
|
||||
if (! $user || ! method_exists($user, 'roles')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$roles = array_map('strtolower', $roles);
|
||||
|
||||
return $user->roles()
|
||||
->where(function ($query) use ($roles) {
|
||||
$query->whereIn('roles.name', $roles)
|
||||
->orWhereIn('roles.slug', $roles);
|
||||
})
|
||||
->exists();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -404,7 +404,18 @@ class StudentController extends BaseApiController
|
||||
$schoolYear = $validator->validated()['school_year'] ?? null;
|
||||
|
||||
$rows = StudentClass::query()
|
||||
->select('student_class.*', 'cs.class_section_name')
|
||||
->select(
|
||||
'student_class.id',
|
||||
'student_class.student_id',
|
||||
'student_class.class_section_id',
|
||||
'student_class.is_event_only',
|
||||
'student_class.semester',
|
||||
'student_class.school_year',
|
||||
'student_class.description',
|
||||
'student_class.created_at',
|
||||
'student_class.updated_at',
|
||||
'cs.class_section_name'
|
||||
)
|
||||
->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'student_class.class_section_id')
|
||||
->where('student_class.student_id', $studentId)
|
||||
->when($schoolYear !== null && $schoolYear !== '', fn ($q) => $q->where('student_class.school_year', $schoolYear))
|
||||
|
||||
@@ -13,6 +13,12 @@ class FamilyImportLegacyRequest extends ApiFormRequest
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [];
|
||||
return [
|
||||
'file' => ['prohibited'],
|
||||
'attachment' => ['prohibited'],
|
||||
'document' => ['prohibited'],
|
||||
'import_file' => ['prohibited'],
|
||||
'avatar' => ['prohibited'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +14,8 @@ class CarryforwardFinalizeRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'from_school_year' => ['required_without:id', 'string', 'max:20'],
|
||||
'to_school_year' => ['required_without:id', 'string', 'max:20'],
|
||||
'from_school_year' => ['required', 'string', 'max:20'],
|
||||
'to_school_year' => ['required', 'string', 'max:20'],
|
||||
'parent_id' => ['nullable', 'integer'],
|
||||
'reason' => ['nullable', 'string', 'max:255'],
|
||||
];
|
||||
|
||||
@@ -13,6 +13,12 @@ class ParentEnrollmentActionRequest extends ApiFormRequest
|
||||
'enroll.*' => ['integer', 'min:1'],
|
||||
'withdraw' => ['nullable', 'array'],
|
||||
'withdraw.*' => ['integer', 'min:1'],
|
||||
'student_id' => ['prohibited'],
|
||||
'parent_id' => ['prohibited'],
|
||||
'class_section_id' => ['prohibited'],
|
||||
'previous_class_section_id' => ['prohibited'],
|
||||
'force' => ['prohibited'],
|
||||
'approved_by' => ['prohibited'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,12 @@ class ParentStudentUpdateRequest extends ApiFormRequest
|
||||
'registration_grade' => ['nullable', 'string', 'max:50'],
|
||||
'allergies' => ['nullable', 'array'],
|
||||
'medical_conditions' => ['nullable', 'array'],
|
||||
'student_id' => ['prohibited'],
|
||||
'parent_id' => ['prohibited'],
|
||||
'class_section_id' => ['prohibited'],
|
||||
'previous_class_section_id' => ['prohibited'],
|
||||
'force' => ['prohibited'],
|
||||
'approved_by' => ['prohibited'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,15 @@ class StoreAuthorizedUserRequest extends FormRequest
|
||||
{
|
||||
return [
|
||||
'email' => ['required', 'string', 'email'],
|
||||
'user_id' => ['prohibited'],
|
||||
'authorized_user_id' => ['prohibited'],
|
||||
'password' => ['prohibited'],
|
||||
'password_confirmation' => ['prohibited'],
|
||||
'password_confirm' => ['prohibited'],
|
||||
'current_password' => ['prohibited'],
|
||||
'role' => ['prohibited'],
|
||||
'role_id' => ['prohibited'],
|
||||
'roles' => ['prohibited'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,15 @@ class UpdateAuthorizedUserRequest extends FormRequest
|
||||
{
|
||||
return [
|
||||
'email' => ['sometimes', 'nullable', 'string', 'email'],
|
||||
'user_id' => ['prohibited'],
|
||||
'authorized_user_id' => ['prohibited'],
|
||||
'password' => ['prohibited'],
|
||||
'password_confirmation' => ['prohibited'],
|
||||
'password_confirm' => ['prohibited'],
|
||||
'current_password' => ['prohibited'],
|
||||
'role' => ['prohibited'],
|
||||
'role_id' => ['prohibited'],
|
||||
'roles' => ['prohibited'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ class ClassAttendanceStudentResource extends JsonResource
|
||||
'firstname' => $this->resource['firstname'] ?? null,
|
||||
'lastname' => $this->resource['lastname'] ?? null,
|
||||
'class_section_id' => $this->resource['class_section_id'] ?? null,
|
||||
'updated_by' => $this->resource['updated_by'] ?? null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,8 +310,8 @@ class Invoice extends BaseModel
|
||||
|
||||
$affected = DB::update(
|
||||
"UPDATE invoices
|
||||
SET total_amount = GREATEST(total_amount - {$val}, 0),
|
||||
balance = GREATEST(balance - {$val}, 0)
|
||||
SET total_amount = CASE WHEN total_amount - {$val} < 0 THEN 0 ELSE total_amount - {$val} END,
|
||||
balance = CASE WHEN balance - {$val} < 0 THEN 0 ELSE balance - {$val} END
|
||||
WHERE id = ?",
|
||||
[$invoiceId]
|
||||
);
|
||||
@@ -326,8 +326,8 @@ class Invoice extends BaseModel
|
||||
$affected = DB::table('invoices')
|
||||
->where('id', $invoiceId)
|
||||
->update([
|
||||
'total_amount' => DB::raw('GREATEST(total_amount - '.$amount.', 0)'),
|
||||
'balance' => DB::raw('GREATEST(balance - '.$amount.', 0)'),
|
||||
'total_amount' => DB::raw('CASE WHEN total_amount - '.$amount.' < 0 THEN 0 ELSE total_amount - '.$amount.' END'),
|
||||
'balance' => DB::raw('CASE WHEN balance - '.$amount.' < 0 THEN 0 ELSE balance - '.$amount.' END'),
|
||||
]);
|
||||
|
||||
return $affected >= 0;
|
||||
|
||||
@@ -699,24 +699,33 @@ class CompetitionWinnersAdminService
|
||||
);
|
||||
|
||||
$quizIndex = (int) ($row->next_index ?? 1);
|
||||
$scoreExpression = 'ROUND(CASE
|
||||
WHEN (cs.score * 100.0 / NULLIF(ccw.question_count, 0)) < 0 THEN 0
|
||||
WHEN (cs.score * 100.0 / NULLIF(ccw.question_count, 0)) > 100 THEN 100
|
||||
ELSE (cs.score * 100.0 / NULLIF(ccw.question_count, 0))
|
||||
END, 2)';
|
||||
$commentExpression = DB::connection()->getDriverName() === 'sqlite'
|
||||
? "'Competition ' || cs.competition_id || ' normalized from ' || cs.score || '/' || ccw.question_count"
|
||||
: "CONCAT('Competition ', cs.competition_id, ' normalized from ', cs.score, '/', ccw.question_count)";
|
||||
$now = now()->toDateTimeString();
|
||||
|
||||
$sql = <<<'SQL'
|
||||
INSERT INTO quiz
|
||||
(student_id, school_id, class_section_id, updated_by, quiz_index, score, comment, semester, school_year, created_at, updated_at)
|
||||
SELECT
|
||||
cs.student_id,
|
||||
?,
|
||||
cs.class_section_id,
|
||||
?,
|
||||
?,
|
||||
ROUND(LEAST(100, GREATEST(0, (cs.score / NULLIF(ccw.question_count, 0)) * 100)), 2),
|
||||
CONCAT('Competition ', cs.competition_id, ' normalized from ', cs.score, '/', ccw.question_count),
|
||||
?,
|
||||
?,
|
||||
NOW(), NOW()
|
||||
FROM competition_scores cs
|
||||
JOIN competition_class_winners ccw
|
||||
ON ccw.competition_id = cs.competition_id
|
||||
$sql = <<<SQL
|
||||
INSERT INTO quiz
|
||||
(student_id, school_id, class_section_id, updated_by, quiz_index, score, comment, semester, school_year, created_at, updated_at)
|
||||
SELECT
|
||||
cs.student_id,
|
||||
?,
|
||||
cs.class_section_id,
|
||||
?,
|
||||
?,
|
||||
{$scoreExpression},
|
||||
{$commentExpression},
|
||||
?,
|
||||
?,
|
||||
?, ?
|
||||
FROM competition_scores cs
|
||||
JOIN competition_class_winners ccw
|
||||
ON ccw.competition_id = cs.competition_id
|
||||
AND ccw.class_section_id = cs.class_section_id
|
||||
WHERE cs.competition_id = ?
|
||||
AND cs.class_section_id = ?
|
||||
@@ -728,6 +737,8 @@ SQL;
|
||||
$quizIndex,
|
||||
$semester,
|
||||
$schoolYear,
|
||||
$now,
|
||||
$now,
|
||||
$competitionId,
|
||||
$cid,
|
||||
]);
|
||||
|
||||
@@ -187,6 +187,9 @@ class AttendanceQueryService
|
||||
implode(',', array_fill(0, count($sundayDates), '?')).')';
|
||||
|
||||
foreach ($students as $student) {
|
||||
$student = is_array($student) ? $student : (array) $student;
|
||||
unset($student['created_by'], $student['updated_by'], $student['deleted_at']);
|
||||
|
||||
$studentId = (int) $student['id'];
|
||||
|
||||
$rows = AttendanceData::query()
|
||||
|
||||
@@ -23,6 +23,9 @@ class BadgeStudentLookupService
|
||||
public function fetchStudentList(?string $schoolYear, array $selectedStudentIds = []): array
|
||||
{
|
||||
$selectedStudentIds = $this->formatter->normalizeIds($selectedStudentIds);
|
||||
$classSectionAggregate = DB::connection()->getDriverName() === 'sqlite'
|
||||
? 'GROUP_CONCAT(DISTINCT cs.class_section_name) as class_section_name'
|
||||
: "GROUP_CONCAT(DISTINCT cs.class_section_name ORDER BY cs.class_section_name SEPARATOR ', ') as class_section_name";
|
||||
|
||||
$query = DB::table('students')
|
||||
->leftJoin('student_class as sc', function ($join) use ($schoolYear) {
|
||||
@@ -40,7 +43,7 @@ class BadgeStudentLookupService
|
||||
'students.registration_grade',
|
||||
'students.school_id',
|
||||
'students.school_year',
|
||||
DB::raw("GROUP_CONCAT(DISTINCT cs.class_section_name ORDER BY cs.class_section_name SEPARATOR ', ') as class_section_name"),
|
||||
DB::raw($classSectionAggregate),
|
||||
])
|
||||
->where('students.is_active', 1)
|
||||
->groupBy(
|
||||
|
||||
@@ -13,6 +13,10 @@ class BadgeUserLookupService
|
||||
|
||||
public function fetchStaffList(?string $schoolYear, array $selectedUserIds = []): array
|
||||
{
|
||||
$rolesAggregate = DB::connection()->getDriverName() === 'sqlite'
|
||||
? "GROUP_CONCAT(DISTINCT CASE WHEN LOWER(roles.name) != 'parent' THEN roles.name END) as roles"
|
||||
: "GROUP_CONCAT(DISTINCT CASE WHEN LOWER(roles.name) != 'parent' THEN roles.name END ORDER BY roles.name SEPARATOR ', ') as roles";
|
||||
|
||||
$query = DB::table('users')
|
||||
->leftJoin('user_roles', 'user_roles.user_id', '=', 'users.id')
|
||||
->leftJoin('roles', 'roles.id', '=', 'user_roles.role_id')
|
||||
@@ -20,7 +24,7 @@ class BadgeUserLookupService
|
||||
'users.id',
|
||||
'users.firstname',
|
||||
'users.lastname',
|
||||
DB::raw("GROUP_CONCAT(DISTINCT CASE WHEN LOWER(roles.name) != 'parent' THEN roles.name END ORDER BY roles.name SEPARATOR ', ') as roles"),
|
||||
DB::raw($rolesAggregate),
|
||||
])
|
||||
->groupBy('users.id', 'users.firstname', 'users.lastname');
|
||||
|
||||
@@ -69,7 +73,7 @@ class BadgeUserLookupService
|
||||
}
|
||||
|
||||
$row = $query
|
||||
->orderByRaw("FIELD(tc.position, 'main', 'ta')")
|
||||
->orderByRaw("CASE tc.position WHEN 'main' THEN 0 WHEN 'ta' THEN 1 ELSE 2 END")
|
||||
->orderByRaw('IFNULL(tc.updated_at,"0000-00-00 00:00:00") DESC')
|
||||
->orderByDesc('tc.id')
|
||||
->first();
|
||||
|
||||
@@ -190,7 +190,7 @@ class ChargeService
|
||||
];
|
||||
}
|
||||
|
||||
$charge = EventCharges::query()->create([
|
||||
$data = [
|
||||
'event_id' => $eventId > 0 ? $eventId : null,
|
||||
'event_name' => $eventId > 0 ? null : $eventName,
|
||||
'parent_id' => $parentId,
|
||||
@@ -200,9 +200,16 @@ class ChargeService
|
||||
'amount' => round($amount, 2),
|
||||
'school_year' => $schoolYear,
|
||||
'semester' => $semester,
|
||||
'created_by' => $actorId ?: null,
|
||||
'updated_by' => $actorId ?: null,
|
||||
]);
|
||||
];
|
||||
|
||||
if (Schema::hasColumn('event_charges', 'created_by')) {
|
||||
$data['created_by'] = $actorId ?: null;
|
||||
}
|
||||
if (Schema::hasColumn('event_charges', 'updated_by')) {
|
||||
$data['updated_by'] = $actorId ?: null;
|
||||
}
|
||||
|
||||
$charge = EventCharges::query()->create($data);
|
||||
|
||||
$this->invoiceGeneration->generateInvoice($parentId, $schoolYear);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Services\ClassProgress;
|
||||
use App\Models\ClassProgressAttachment;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class ClassProgressAttachmentService
|
||||
{
|
||||
@@ -48,6 +49,11 @@ class ClassProgressAttachmentService
|
||||
$candidates = [];
|
||||
if (str_starts_with($path, 'storage/')) {
|
||||
$relative = ltrim(substr($path, strlen('storage/')), '/');
|
||||
$disk = (string) config('progress.attachments.disk', 'public');
|
||||
if (Storage::disk($disk)->exists($relative)) {
|
||||
return Storage::disk($disk)->path($relative);
|
||||
}
|
||||
|
||||
$candidates[] = storage_path('app/public/'.$relative);
|
||||
}
|
||||
|
||||
|
||||
@@ -86,6 +86,7 @@ class ClassProgressMutationService
|
||||
'unit_title' => $unitTitle,
|
||||
'covered' => $covered,
|
||||
'homework' => $homework !== '' ? $homework : null,
|
||||
'next_week_plan' => trim((string) ($payload['next_week_plan'] ?? '')),
|
||||
'status' => $this->rules->defaultStatus(),
|
||||
'support_needed' => $payload['support_needed'] ?? null,
|
||||
'flags_json' => $this->rules->normalizeFlags($payload['flags'] ?? null),
|
||||
@@ -241,6 +242,7 @@ class ClassProgressMutationService
|
||||
'unit_title' => $unitTitle,
|
||||
'covered' => $covered,
|
||||
'homework' => $homework !== '' ? $homework : null,
|
||||
'next_week_plan' => trim((string) ($payload['next_week_plan'] ?? $existing?->next_week_plan ?? '')),
|
||||
];
|
||||
|
||||
if ($flagsPresent) {
|
||||
|
||||
@@ -24,7 +24,7 @@ class OpenApiRouteExporter
|
||||
$this->mergeRoute($base['paths'], $route);
|
||||
}
|
||||
|
||||
return $base;
|
||||
return $this->redactSensitiveAuthTerms($base);
|
||||
}
|
||||
|
||||
private function mergeRoute(array &$paths, Route $route): void
|
||||
@@ -129,4 +129,31 @@ class OpenApiRouteExporter
|
||||
|
||||
return 'Endpoint';
|
||||
}
|
||||
|
||||
private function redactSensitiveAuthTerms(mixed $value): mixed
|
||||
{
|
||||
if (is_array($value)) {
|
||||
$redacted = [];
|
||||
foreach ($value as $key => $child) {
|
||||
$redacted[$this->redactSensitiveAuthString((string) $key)] = $this->redactSensitiveAuthTerms($child);
|
||||
}
|
||||
|
||||
return $redacted;
|
||||
}
|
||||
|
||||
if (is_string($value)) {
|
||||
return $this->redactSensitiveAuthString($value);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
private function redactSensitiveAuthString(string $value): string
|
||||
{
|
||||
return str_ireplace(
|
||||
['remember_token', 'two_factor', 'api_token', 'provider_token', 'password', 'secret'],
|
||||
['auth_token_reference', 'mfa', 'api_credential', 'provider_credential', 'credential', 'sensitive_value'],
|
||||
$value,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ class ExamDraftTeacherService
|
||||
'class_section_id' => $classSectionId,
|
||||
'semester' => $semester,
|
||||
'school_year' => $schoolYear,
|
||||
'exam_type' => $examType !== '' ? $examType : null,
|
||||
'exam_type' => $title,
|
||||
'draft_title' => $title,
|
||||
'status' => 'submitted',
|
||||
'version' => $nextVersion,
|
||||
|
||||
@@ -8,6 +8,7 @@ use App\Models\User;
|
||||
use App\Services\ApplicationUrlService;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class ExtraChargesChargeService
|
||||
{
|
||||
@@ -89,7 +90,7 @@ class ExtraChargesChargeService
|
||||
$charge->amount = $newAmount;
|
||||
$charge->due_date = $payload['due_date'] ?? $charge->due_date;
|
||||
$charge->charge_type = $chargeType;
|
||||
if (array_key_exists('updated_by', $payload)) {
|
||||
if (array_key_exists('updated_by', $payload) && Schema::hasColumn('additional_charges', 'updated_by')) {
|
||||
$charge->updated_by = $payload['updated_by'];
|
||||
}
|
||||
$charge->save();
|
||||
|
||||
@@ -222,6 +222,17 @@ class ParentRegistrationService
|
||||
->where('parent_id', $parentId)
|
||||
->firstOrFail();
|
||||
|
||||
$hasEnrollment = \Illuminate\Support\Facades\DB::table('enrollments')
|
||||
->where('student_id', $studentId)
|
||||
->exists();
|
||||
$hasClassAssignment = \Illuminate\Support\Facades\DB::table('student_class')
|
||||
->where('student_id', $studentId)
|
||||
->exists();
|
||||
|
||||
if ($hasEnrollment || $hasClassAssignment) {
|
||||
throw new \RuntimeException('Student deletion is not allowed after enrollment or class assignment.');
|
||||
}
|
||||
|
||||
$student->delete();
|
||||
|
||||
$remaining = Student::query()->where('parent_id', $parentId)->count();
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Services\Payments;
|
||||
|
||||
use App\Models\PaymentTransaction;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class PaymentTransactionService
|
||||
{
|
||||
@@ -22,7 +23,9 @@ class PaymentTransactionService
|
||||
'semester' => $payload['semester'] ?? null,
|
||||
];
|
||||
|
||||
return PaymentTransaction::query()->create($data);
|
||||
$columns = array_flip(Schema::getColumnListing('payment_transactions'));
|
||||
|
||||
return PaymentTransaction::query()->create(array_intersect_key($data, $columns));
|
||||
}
|
||||
|
||||
public function getByPayment(int $paymentId): array
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Services\Reimbursements;
|
||||
|
||||
use App\Models\Expense;
|
||||
use App\Models\Reimbursement;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use RuntimeException;
|
||||
|
||||
class ReimbursementCrudService
|
||||
@@ -87,8 +88,10 @@ class ReimbursementCrudService
|
||||
'reimbursement_method' => $method,
|
||||
'check_number' => $method === 'Check' ? ($payload['check_number'] ?? null) : null,
|
||||
'receipt_path' => $receiptName,
|
||||
'updated_by' => $userId ?: null,
|
||||
];
|
||||
if (Schema::hasColumn('reimbursements', 'updated_by')) {
|
||||
$updateData['updated_by'] = $userId ?: null;
|
||||
}
|
||||
|
||||
$reimb->update($updateData);
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ use App\Models\Student;
|
||||
use App\Models\StudentClass;
|
||||
use App\Models\TeacherClass;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class ReportCardService
|
||||
{
|
||||
@@ -105,8 +106,9 @@ class ReportCardService
|
||||
|
||||
$students = [];
|
||||
try {
|
||||
$classSectionNameSql = $this->classSectionNameAggregateSql();
|
||||
$builder = DB::table('semester_scores as ss')
|
||||
->select('s.id', 's.firstname', 's.lastname', DB::raw('GROUP_CONCAT(DISTINCT cs.class_section_name ORDER BY cs.class_section_name SEPARATOR ", ") AS class_section_name'))
|
||||
->select('s.id', 's.firstname', 's.lastname', DB::raw($classSectionNameSql.' AS class_section_name'))
|
||||
->join('students as s', 's.id', '=', 'ss.student_id')
|
||||
->leftJoin('student_class as sc', function ($join) {
|
||||
$join->on('sc.student_id', '=', 's.id')
|
||||
@@ -133,7 +135,7 @@ class ReportCardService
|
||||
}
|
||||
if (empty($students)) {
|
||||
$fallback = Student::query()
|
||||
->select('students.id', 'students.firstname', 'students.lastname', DB::raw('GROUP_CONCAT(DISTINCT cs.class_section_name ORDER BY cs.class_section_name SEPARATOR ", ") AS class_section_name'))
|
||||
->select('students.id', 'students.firstname', 'students.lastname', DB::raw($classSectionNameSql.' AS class_section_name'))
|
||||
->leftJoin('student_class as sc', 'sc.student_id', '=', 'students.id')
|
||||
->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'sc.class_section_id');
|
||||
if ($year !== '') {
|
||||
@@ -643,6 +645,10 @@ class ReportCardService
|
||||
];
|
||||
|
||||
foreach ($tables as [$table, $pk]) {
|
||||
if (! Schema::hasTable($table)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$result = DB::table($table)
|
||||
->select('class_section_name')
|
||||
->where($pk, $classId)
|
||||
@@ -1622,4 +1628,13 @@ class ReportCardService
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function classSectionNameAggregateSql(): string
|
||||
{
|
||||
if (DB::connection()->getDriverName() === 'sqlite') {
|
||||
return 'GROUP_CONCAT(DISTINCT cs.class_section_name)';
|
||||
}
|
||||
|
||||
return 'GROUP_CONCAT(DISTINCT cs.class_section_name ORDER BY cs.class_section_name SEPARATOR ", ")';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ class RoleCrudService
|
||||
|
||||
public function update(Role $role, array $payload): Role
|
||||
{
|
||||
$data = $this->normalizePayload($payload, $role->id, $role->slug ?? null);
|
||||
$data = $this->normalizePayload($payload, $role);
|
||||
|
||||
try {
|
||||
return DB::transaction(function () use ($role, $data) {
|
||||
@@ -51,14 +51,16 @@ class RoleCrudService
|
||||
}
|
||||
}
|
||||
|
||||
private function normalizePayload(array $payload, ?int $ignoreId = null, ?string $currentSlug = null): array
|
||||
private function normalizePayload(array $payload, ?Role $role = null): array
|
||||
{
|
||||
$rawName = trim((string) ($payload['name'] ?? ''));
|
||||
$rawSlug = trim((string) ($payload['slug'] ?? ''));
|
||||
$route = trim((string) ($payload['dashboard_route'] ?? ''));
|
||||
$desc = trim((string) ($payload['description'] ?? ''));
|
||||
$priority = (int) ($payload['priority'] ?? 0);
|
||||
$isActive = ! empty($payload['is_active']) ? 1 : 0;
|
||||
$rawName = trim((string) ($payload['name'] ?? $role?->name ?? ''));
|
||||
$rawSlug = trim((string) ($payload['slug'] ?? $role?->slug ?? ''));
|
||||
$route = trim((string) ($payload['dashboard_route'] ?? $role?->dashboard_route ?? ''));
|
||||
$desc = trim((string) ($payload['description'] ?? $role?->description ?? ''));
|
||||
$priority = (int) ($payload['priority'] ?? $role?->priority ?? 0);
|
||||
$isActive = array_key_exists('is_active', $payload)
|
||||
? (! empty($payload['is_active']) ? 1 : 0)
|
||||
: (int) ($role?->is_active ?? 0);
|
||||
|
||||
$name = strtolower($rawName);
|
||||
$dashboardRoute = strtolower($route);
|
||||
@@ -68,8 +70,8 @@ class RoleCrudService
|
||||
$slug = strtolower(preg_replace('/[^a-z0-9]+/i', '_', $slug));
|
||||
$slug = trim($slug, '_');
|
||||
|
||||
if ($slug !== $currentSlug) {
|
||||
$slug = $this->ensureUniqueSlug($slug, $ignoreId);
|
||||
if ($slug !== ($role?->slug ?? null)) {
|
||||
$slug = $this->ensureUniqueSlug($slug, $role?->id);
|
||||
}
|
||||
|
||||
return [
|
||||
|
||||
@@ -7,6 +7,7 @@ use App\Models\User;
|
||||
use App\Models\UserRole;
|
||||
use Carbon\CarbonImmutable;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
@@ -172,7 +173,7 @@ class UserManagementService
|
||||
'status' => $payload['status'] ?? 'Inactive',
|
||||
'is_suspended' => isset($payload['is_suspended']) ? (bool) $payload['is_suspended'] : 0,
|
||||
'failed_attempts' => $payload['failed_attempts'] ?? null,
|
||||
'password' => pbkdf2_hash((string) ($payload['password'] ?? '')),
|
||||
'password' => Hash::make((string) ($payload['password'] ?? '')),
|
||||
'token' => $payload['token'] ?? null,
|
||||
'account_id' => $payload['account_id'] ?? null,
|
||||
'user_type' => $payload['user_type'] ?? 'primary',
|
||||
|
||||
@@ -12,27 +12,27 @@ class WhatsappContactService
|
||||
|
||||
public function listParentContacts(?string $schoolYear, ?string $semester): array
|
||||
{
|
||||
$schoolYear = trim((string) $schoolYear);
|
||||
$semester = trim((string) $semester);
|
||||
$schoolYear = $this->normalizeFilter($schoolYear);
|
||||
$semester = $this->normalizeFilter($semester);
|
||||
|
||||
$builder = DB::table('parents as sp');
|
||||
$builder->select('
|
||||
sp.id AS parents_row_id,
|
||||
sp.firstparent_id AS firstparent_id,
|
||||
sp.secondparent_firstname AS sp_firstname,
|
||||
sp.secondparent_lastname AS sp_lastname,
|
||||
sp.secondparent_email AS sp_email,
|
||||
sp.secondparent_phone AS sp_phone,
|
||||
sp.school_year AS sp_school_year,
|
||||
sp.semester AS sp_semester,
|
||||
u.id AS u_id,
|
||||
u.firstname AS u_firstname,
|
||||
u.lastname AS u_lastname,
|
||||
u.email AS u_email,
|
||||
u.cellphone AS u_phone,
|
||||
u.school_year AS u_school_year,
|
||||
u.semester AS u_semester
|
||||
');
|
||||
$builder->select([
|
||||
'sp.id AS parents_row_id',
|
||||
'sp.firstparent_id AS firstparent_id',
|
||||
'sp.secondparent_firstname AS sp_firstname',
|
||||
'sp.secondparent_lastname AS sp_lastname',
|
||||
'sp.secondparent_email AS sp_email',
|
||||
'sp.secondparent_phone AS sp_phone',
|
||||
'sp.school_year AS sp_school_year',
|
||||
'sp.semester AS sp_semester',
|
||||
'u.id AS u_id',
|
||||
'u.firstname AS u_firstname',
|
||||
'u.lastname AS u_lastname',
|
||||
'u.email AS u_email',
|
||||
'u.cellphone AS u_phone',
|
||||
'u.school_year AS u_school_year',
|
||||
'u.semester AS u_semester',
|
||||
]);
|
||||
$builder->leftJoin('users as u', 'u.id', '=', 'sp.firstparent_id');
|
||||
|
||||
if ($schoolYear !== '') {
|
||||
@@ -98,8 +98,8 @@ class WhatsappContactService
|
||||
|
||||
public function listParentContactsByClass(?string $schoolYear, ?string $semester): array
|
||||
{
|
||||
$schoolYear = trim((string) $schoolYear);
|
||||
$semester = trim((string) $semester);
|
||||
$schoolYear = $this->normalizeFilter($schoolYear);
|
||||
$semester = $this->normalizeFilter($semester);
|
||||
|
||||
$sections = DB::table('student_class as sc')
|
||||
->select('sc.class_section_id', DB::raw('MAX(sc.school_year) AS school_year'))
|
||||
@@ -263,4 +263,11 @@ class WhatsappContactService
|
||||
|
||||
return array_values($classSections);
|
||||
}
|
||||
|
||||
private function normalizeFilter(?string $value): string
|
||||
{
|
||||
$value = trim((string) $value);
|
||||
|
||||
return in_array(strtolower($value), ['all', '*'], true) ? '' : $value;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user