first commit

This commit is contained in:
Saufi
2026-06-24 20:32:14 +08:00
commit 10fb30ad69
201 changed files with 21356 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Spatie\Activitylog\Models\Activity;
class AuditLogController extends Controller
{
public function index(Request $request)
{
abort_unless(auth()->user()->can('pengguna.urus') || auth()->user()->hasRole('Super Admin'), 403);
$logs = Activity::with('causer')
->when($request->filled('log'), fn ($q) => $q->where('log_name', $request->log))
->latest()->paginate(30)->withQueryString();
$logNames = Activity::query()->distinct()->pluck('log_name')->filter()->values();
return view('audit.index', compact('logs', 'logNames'));
}
}

View File

@@ -0,0 +1,69 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\ValidationException;
class LoginController extends Controller
{
public function showLoginForm()
{
if (Auth::check()) {
return redirect()->route('dashboard');
}
return view('auth.login');
}
public function login(Request $request)
{
$credentials = $request->validate([
'email' => ['required', 'email'],
'password' => ['required', 'string'],
], [], [
'email' => 'emel',
'password' => 'kata laluan',
]);
$remember = $request->boolean('remember');
if (! Auth::attempt($credentials, $remember)) {
activity('auth')->withProperties(['email' => $request->email])->log('Cubaan log masuk gagal');
throw ValidationException::withMessages([
'email' => 'Emel atau kata laluan tidak sah.',
]);
}
$user = Auth::user();
if (! $user->is_active) {
Auth::logout();
throw ValidationException::withMessages([
'email' => 'Akaun anda telah dinyahaktifkan. Sila hubungi pentadbir.',
]);
}
$request->session()->regenerate();
activity('auth')->causedBy($user)->log('Pengguna log masuk');
return redirect()->intended(route('dashboard'));
}
public function logout(Request $request)
{
$user = Auth::user();
activity('auth')->causedBy($user)->log('Pengguna log keluar');
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect()->route('login');
}
}

View File

@@ -0,0 +1,188 @@
<?php
namespace App\Http\Controllers;
use App\Models\ChecklistFormula;
use App\Models\ChecklistItem;
use App\Models\ChecklistScope;
use App\Models\ChecklistSection;
use App\Models\ChecklistTemplate;
use Illuminate\Http\Request;
/**
* Mengurus elemen bersarang templat senarai semak (seksyen, skop, item, formula).
* Semua tindakan memerlukan kebenaran 'templat.urus'.
*/
class ChecklistBuilderController extends Controller
{
// Kebenaran 'templat.urus' dikuatkuasakan pada peringkat laluan (route group).
// ---------------- Seksyen ----------------
public function storeSection(Request $request, ChecklistTemplate $template)
{
$data = $this->validateSection($request);
$template->sections()->create($data);
return back()->with('success', 'Seksyen ditambah.');
}
public function updateSection(Request $request, ChecklistSection $section)
{
$section->update($this->validateSection($request));
return back()->with('success', 'Seksyen dikemas kini.');
}
public function destroySection(ChecklistSection $section)
{
$section->delete();
return back()->with('success', 'Seksyen dipadam.');
}
protected function validateSection(Request $request): array
{
$data = $request->validate([
'code' => ['required', 'string', 'max:50'],
'title' => ['required', 'string', 'max:255'],
'description' => ['nullable', 'string', 'max:1000'],
'order' => ['nullable', 'integer', 'min:0'],
'is_formula_enabled' => ['nullable', 'boolean'],
]);
$data['is_formula_enabled'] = $request->boolean('is_formula_enabled');
$data['order'] ??= 0;
return $data;
}
// ---------------- Skop ----------------
public function storeScope(Request $request, ChecklistSection $section)
{
$section->scopes()->create($this->validateScope($request));
return back()->with('success', 'Skop ditambah.');
}
public function updateScope(Request $request, ChecklistScope $scope)
{
$scope->update($this->validateScope($request));
return back()->with('success', 'Skop dikemas kini.');
}
public function destroyScope(ChecklistScope $scope)
{
$scope->delete();
return back()->with('success', 'Skop dipadam.');
}
protected function validateScope(Request $request): array
{
$data = $request->validate([
'code' => ['required', 'string', 'max:50'],
'title' => ['required', 'string', 'max:255'],
'description' => ['nullable', 'string', 'max:1000'],
'order' => ['nullable', 'integer', 'min:0'],
]);
$data['order'] ??= 0;
return $data;
}
// ---------------- Item ----------------
public function storeItem(Request $request, ChecklistSection $section)
{
$data = $this->validateItem($request);
$section->items()->create($data);
return back()->with('success', 'Item ditambah.');
}
public function updateItem(Request $request, ChecklistItem $item)
{
$item->update($this->validateItem($request));
return back()->with('success', 'Item dikemas kini.');
}
public function destroyItem(ChecklistItem $item)
{
$item->delete();
return back()->with('success', 'Item dipadam.');
}
protected function validateItem(Request $request): array
{
$data = $request->validate([
'checklist_scope_id' => ['nullable', 'exists:checklist_scopes,id'],
'code' => ['required', 'string', 'max:50'],
'formula_token' => ['nullable', 'string', 'max:50', 'regex:/^[A-Za-z_][A-Za-z0-9_]*$/'],
'label' => ['required', 'string', 'max:500'],
'description' => ['nullable', 'string', 'max:1000'],
'input_type' => ['required', 'in:'.implode(',', array_keys(ChecklistItem::INPUT_TYPES))],
'options' => ['nullable', 'string'],
'unit' => ['nullable', 'string', 'max:50'],
'is_required' => ['nullable', 'boolean'],
'is_calculated' => ['nullable', 'boolean'],
'formula' => ['nullable', 'string', 'max:500'],
'order' => ['nullable', 'integer', 'min:0'],
], [], ['formula_token' => 'token formula']);
$data['is_required'] = $request->boolean('is_required');
$data['is_calculated'] = $request->boolean('is_calculated');
$data['order'] ??= 0;
// Tukar senarai pilihan (satu baris satu pilihan) kepada array.
if (! empty($data['options'])) {
$data['options'] = collect(preg_split('/\r\n|\r|\n/', $data['options']))
->map(fn ($v) => trim($v))->filter()->values()->all();
} else {
$data['options'] = null;
}
return $data;
}
// ---------------- Formula ----------------
public function storeFormula(Request $request, ChecklistTemplate $template)
{
$data = $this->validateFormula($request);
$template->formulas()->create($data);
return back()->with('success', 'Formula ditambah.');
}
public function updateFormula(Request $request, ChecklistFormula $formula)
{
$formula->update($this->validateFormula($request));
return back()->with('success', 'Formula dikemas kini.');
}
public function destroyFormula(ChecklistFormula $formula)
{
$formula->delete();
return back()->with('success', 'Formula dipadam.');
}
protected function validateFormula(Request $request): array
{
$data = $request->validate([
'checklist_section_id' => ['nullable', 'exists:checklist_sections,id'],
'result_token' => ['required', 'string', 'max:50', 'regex:/^[A-Za-z_][A-Za-z0-9_]*$/'],
'label' => ['nullable', 'string', 'max:255'],
'expression' => ['required', 'string', 'max:500'],
'unit' => ['nullable', 'string', 'max:50'],
'guard_div_zero' => ['nullable', 'boolean'],
'order' => ['nullable', 'integer', 'min:0'],
], [], ['result_token' => 'token hasil', 'expression' => 'ungkapan']);
$data['guard_div_zero'] = $request->boolean('guard_div_zero');
$data['order'] ??= 0;
return $data;
}
}

View File

@@ -0,0 +1,110 @@
<?php
namespace App\Http\Controllers;
use App\Models\ChecklistTemplate;
use Illuminate\Http\Request;
class ChecklistTemplateController extends Controller
{
public function index()
{
$this->authorize('viewAny', ChecklistTemplate::class);
$templates = ChecklistTemplate::withCount('sections')->orderByDesc('is_active')->orderBy('name')->get();
return view('checklist.templates.index', compact('templates'));
}
public function create()
{
$this->authorize('create', ChecklistTemplate::class);
return view('checklist.templates.create', ['template' => new ChecklistTemplate(['version' => '1.0'])]);
}
public function store(Request $request)
{
$this->authorize('create', ChecklistTemplate::class);
$data = $request->validate([
'name' => ['required', 'string', 'max:255'],
'version' => ['required', 'string', 'max:50'],
'description' => ['nullable', 'string', 'max:1000'],
'is_active' => ['nullable', 'boolean'],
]);
$template = ChecklistTemplate::create([
'name' => $data['name'],
'version' => $data['version'],
'description' => $data['description'] ?? null,
'is_active' => $request->boolean('is_active'),
]);
if ($template->is_active) {
ChecklistTemplate::where('id', '!=', $template->id)->update(['is_active' => false]);
}
return redirect()->route('checklist-templates.builder', $template)
->with('success', 'Templat dicipta. Sila bina seksyen dan item.');
}
/** Skrin pembina templat (seksyen, skop, item, formula). */
public function builder(ChecklistTemplate $checklistTemplate)
{
$this->authorize('view', $checklistTemplate);
$checklistTemplate->load([
'sections.scopes',
'sections.items.scope',
'sections.formula',
'formulas',
]);
return view('checklist.templates.builder', ['template' => $checklistTemplate]);
}
public function update(Request $request, ChecklistTemplate $checklistTemplate)
{
$this->authorize('update', $checklistTemplate);
$data = $request->validate([
'name' => ['required', 'string', 'max:255'],
'version' => ['required', 'string', 'max:50'],
'description' => ['nullable', 'string', 'max:1000'],
'is_active' => ['nullable', 'boolean'],
]);
$checklistTemplate->update([
'name' => $data['name'],
'version' => $data['version'],
'description' => $data['description'] ?? null,
'is_active' => $request->boolean('is_active'),
]);
if ($checklistTemplate->is_active) {
ChecklistTemplate::where('id', '!=', $checklistTemplate->id)->update(['is_active' => false]);
}
return back()->with('success', 'Templat dikemas kini.');
}
public function activate(ChecklistTemplate $checklistTemplate)
{
$this->authorize('update', $checklistTemplate);
ChecklistTemplate::query()->update(['is_active' => false]);
$checklistTemplate->update(['is_active' => true]);
return back()->with('success', 'Templat ditetapkan sebagai aktif.');
}
public function destroy(ChecklistTemplate $checklistTemplate)
{
$this->authorize('delete', $checklistTemplate);
$checklistTemplate->delete();
return redirect()->route('checklist-templates.index')->with('success', 'Templat dipadam.');
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace App\Http\Controllers;
use App\Models\Submission;
use App\Models\SubmissionComment;
use Illuminate\Http\Request;
class CommentController extends Controller
{
public function store(Request $request, Submission $submission)
{
$this->authorize('comment', $submission);
$data = $request->validate([
'body' => ['required', 'string', 'max:2000'],
'is_internal' => ['nullable', 'boolean'],
], [], ['body' => 'mesej']);
// Hanya kakitangan JPP boleh hantar komen dalaman.
$isInternal = $request->boolean('is_internal') && auth()->user()->can('serahan.lihat_semua');
$submission->comments()->create([
'user_id' => auth()->id(),
'body' => $data['body'],
'is_internal' => $isInternal,
]);
return back()->with('success', 'Mesej dihantar.')->withFragment('komen');
}
/** Soft delete — hanya pentadbir (kebenaran komen.padam), dengan rekod audit. */
public function destroy(SubmissionComment $comment)
{
abort_unless(auth()->user()->can('komen.padam'), 403);
$comment->update(['deleted_by' => auth()->id()]);
$comment->delete();
activity('komen')->performedOn($comment->submission)->causedBy(auth()->user())
->withProperties(['comment_id' => $comment->id])->log('Komen dipadam (soft delete)');
return back()->with('success', 'Mesej dipadam (rekod audit disimpan).');
}
}

View File

@@ -0,0 +1,132 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\ConsultantRequest;
use App\Models\Consultant;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
class ConsultantController extends Controller
{
public function index(Request $request)
{
$this->authorize('viewAny', Consultant::class);
$query = Consultant::query()->withCount('dataCentres');
if ($search = $request->string('cari')->toString()) {
$query->where(function ($q) use ($search) {
$q->where('nama_perunding', 'like', "%$search%")
->orWhere('no_pendaftaran_syarikat', 'like', "%$search%")
->orWhere('emel', 'like', "%$search%");
});
}
if ($request->filled('status')) {
$query->where('aktif', $request->status === 'aktif');
}
$consultants = $query->orderBy('nama_perunding')->paginate(15)->withQueryString();
return view('consultants.index', compact('consultants'));
}
public function create()
{
$this->authorize('create', Consultant::class);
return view('consultants.create', ['consultant' => new Consultant]);
}
public function store(ConsultantRequest $request)
{
$consultant = DB::transaction(function () use ($request) {
$userId = null;
if ($request->boolean('buat_akaun')) {
$user = User::create([
'name' => $request->user_name,
'email' => $request->user_email,
'password' => Hash::make($request->user_password),
'is_active' => true,
'email_verified_at' => now(),
]);
$user->assignRole('Perunding');
$userId = $user->id;
}
return Consultant::create([
'user_id' => $userId,
'nama_perunding' => $request->nama_perunding,
'no_pendaftaran_syarikat' => $request->no_pendaftaran_syarikat,
'alamat' => $request->alamat,
'emel' => $request->emel,
'telefon' => $request->telefon,
'aktif' => $request->boolean('aktif'),
]);
});
return redirect()->route('consultants.show', $consultant)
->with('success', 'Perunding berjaya didaftarkan.');
}
public function show(Consultant $consultant)
{
$this->authorize('view', $consultant);
$consultant->load(['user', 'dataCentres', 'assignments.dataCentre', 'assignments.assignedBy']);
return view('consultants.show', compact('consultant'));
}
public function edit(Consultant $consultant)
{
$this->authorize('update', $consultant);
return view('consultants.edit', compact('consultant'));
}
public function update(ConsultantRequest $request, Consultant $consultant)
{
$this->authorize('update', $consultant);
DB::transaction(function () use ($request, $consultant) {
$consultant->update([
'nama_perunding' => $request->nama_perunding,
'no_pendaftaran_syarikat' => $request->no_pendaftaran_syarikat,
'alamat' => $request->alamat,
'emel' => $request->emel,
'telefon' => $request->telefon,
'aktif' => $request->boolean('aktif'),
]);
if ($request->boolean('buat_akaun') && ! $consultant->user_id) {
$user = User::create([
'name' => $request->user_name,
'email' => $request->user_email,
'password' => Hash::make($request->user_password),
'is_active' => true,
'email_verified_at' => now(),
]);
$user->assignRole('Perunding');
$consultant->update(['user_id' => $user->id]);
}
});
return redirect()->route('consultants.show', $consultant)
->with('success', 'Maklumat perunding dikemas kini.');
}
public function destroy(Consultant $consultant)
{
$this->authorize('delete', $consultant);
$consultant->delete();
return redirect()->route('consultants.index')
->with('success', 'Perunding telah dipadam.');
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Validation\ValidatesRequests;
abstract class Controller
{
use AuthorizesRequests, ValidatesRequests;
}

View File

@@ -0,0 +1,71 @@
<?php
namespace App\Http\Controllers;
use App\Models\Submission;
use App\Models\SubmissionCorrectionRequest;
use Illuminate\Http\Request;
class CorrectionRequestController extends Controller
{
/** Pegawai cipta permohonan pembetulan (wajib: no muka surat, lokasi, penerangan). */
public function store(Request $request, Submission $submission)
{
$this->authorize('review', $submission);
$data = $request->validate([
'checklist_item_id' => ['nullable', 'exists:checklist_items,id'],
'page_reference' => ['required', 'string', 'max:50'],
'location' => ['required', 'string', 'max:255'],
'description' => ['required', 'string', 'max:2000'],
], [], [
'page_reference' => 'no muka surat',
'location' => 'lokasi/seksyen/item',
'description' => 'penerangan pembetulan',
]);
$submission->correctionRequests()->create(array_merge($data, [
'status' => 'terbuka',
'created_by' => auth()->id(),
]));
activity('serahan')->performedOn($submission)->causedBy(auth()->user())
->log('Permohonan pembetulan dibuat');
return back()->with('success', 'Permohonan pembetulan ditambah.');
}
/** Perunding jawab permohonan pembetulan. */
public function respond(Request $request, SubmissionCorrectionRequest $correction)
{
$submission = $correction->submission;
$this->authorize('respondCorrection', $submission);
$data = $request->validate([
'consultant_response' => ['required', 'string', 'max:2000'],
], [], ['consultant_response' => 'maklum balas']);
$correction->update([
'consultant_response' => $data['consultant_response'],
'status' => 'dijawab',
'responded_at' => now(),
'responded_by' => auth()->id(),
]);
return back()->with('success', 'Maklum balas pembetulan dihantar.');
}
/** Pegawai tutup permohonan pembetulan. */
public function resolve(SubmissionCorrectionRequest $correction)
{
$this->authorize('review', $correction->submission);
$correction->update([
'status' => 'selesai',
'resolved_at' => now(),
'resolved_by' => auth()->id(),
]);
return back()->with('success', 'Permohonan pembetulan ditandakan selesai.');
}
}

View File

@@ -0,0 +1,118 @@
<?php
namespace App\Http\Controllers;
use App\Models\Consultant;
use App\Models\DataCentre;
use App\Models\ReportingCycle;
use App\Models\Submission;
use App\Models\SubmissionResult;
use Illuminate\Support\Facades\DB;
class DashboardController extends Controller
{
public function index()
{
$user = auth()->user();
if ($user->isPerunding()) {
return $this->consultantDashboard();
}
return $this->jppDashboard();
}
protected function jppDashboard()
{
$activeCycle = ReportingCycle::aktif()->orderByDesc('renewal_year')->first();
$totalDataCentres = DataCentre::aktif()->count();
$activeConsultants = Consultant::aktif()->count();
$submittedCount = 0;
$notSubmittedCount = 0;
$overdueCount = 0;
$statusBreakdown = collect();
if ($activeCycle) {
$expected = DataCentre::aktif()->whereNotNull('current_consultant_id')->count();
$submittedCount = Submission::where('reporting_cycle_id', $activeCycle->id)
->where('status', '!=', 'draf')->count();
$notSubmittedCount = max(0, $expected - $submittedCount);
$statusBreakdown = Submission::where('reporting_cycle_id', $activeCycle->id)
->select('status', DB::raw('count(*) as jumlah'))
->groupBy('status')->pluck('jumlah', 'status');
if ($activeCycle->isOverdue()) {
$overdueCount = $notSubmittedCount;
}
}
// Jumlah karbon A,B,C,D,E mengikut tahun pelaporan
$carbonByYear = SubmissionResult::query()
->join('submissions', 'submissions.id', '=', 'submission_results.submission_id')
->join('reporting_cycles', 'reporting_cycles.id', '=', 'submissions.reporting_cycle_id')
->whereNull('submissions.deleted_at')
->select(
'reporting_cycles.reporting_year',
'submission_results.result_token',
DB::raw('SUM(submission_results.value) as jumlah')
)
->groupBy('reporting_cycles.reporting_year', 'submission_results.result_token')
->get()
->groupBy('reporting_year');
// Pusat data tertinggi mengikut anggaran karbon (token A)
$topDataCentres = SubmissionResult::query()
->where('submission_results.result_token', 'A')
->join('submissions', 'submissions.id', '=', 'submission_results.submission_id')
->join('data_centres', 'data_centres.id', '=', 'submissions.data_centre_id')
->whereNull('submissions.deleted_at')
->select('data_centres.nama_pusat_data', DB::raw('SUM(submission_results.value) as jumlah'))
->groupBy('data_centres.nama_pusat_data')
->orderByDesc('jumlah')
->limit(10)
->get();
return view('dashboard.jpp', compact(
'activeCycle', 'totalDataCentres', 'activeConsultants',
'submittedCount', 'notSubmittedCount', 'overdueCount',
'statusBreakdown', 'carbonByYear', 'topDataCentres'
));
}
protected function consultantDashboard()
{
$consultant = auth()->user()->consultant;
$dataCentres = collect();
$submissions = collect();
$pendingCount = 0;
$correctionCount = 0;
$activeCycle = ReportingCycle::aktif()->orderByDesc('renewal_year')->first();
if ($consultant) {
$dataCentres = DataCentre::where('current_consultant_id', $consultant->id)->get();
$submissions = Submission::where('consultant_id', $consultant->id)
->with(['dataCentre', 'reportingCycle'])
->latest()->get();
$correctionCount = $submissions->where('status', 'pembetulan_perunding')->count();
if ($activeCycle) {
$submittedDcIds = Submission::where('consultant_id', $consultant->id)
->where('reporting_cycle_id', $activeCycle->id)
->where('status', '!=', 'draf')
->pluck('data_centre_id');
$pendingCount = $dataCentres->whereNotIn('id', $submittedDcIds)->count();
}
}
return view('dashboard.consultant', compact(
'consultant', 'dataCentres', 'submissions',
'pendingCount', 'correctionCount', 'activeCycle'
));
}
}

View File

@@ -0,0 +1,134 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\DataCentreRequest;
use App\Models\Consultant;
use App\Models\DataCentre;
use App\Services\AssignmentService;
use Illuminate\Http\Request;
class DataCentreController extends Controller
{
public function __construct(protected AssignmentService $assignmentService) {}
public function index(Request $request)
{
$this->authorize('viewAny', DataCentre::class);
$user = auth()->user();
$query = DataCentre::query()->with('currentConsultant');
// Perunding hanya lihat pusat data yang ditugaskan kepadanya.
if ($user->isPerunding() && ! $user->can('pusat_data.urus')) {
$query->where('current_consultant_id', $user->consultant?->id ?? 0);
}
if ($search = $request->string('cari')->toString()) {
$query->where('nama_pusat_data', 'like', "%$search%")
->orWhere('tajuk_permohonan', 'like', "%$search%");
}
if ($request->filled('status')) {
$query->where('status', $request->status);
}
$dataCentres = $query->orderBy('nama_pusat_data')->paginate(15)->withQueryString();
return view('data_centres.index', compact('dataCentres'));
}
public function create()
{
$this->authorize('create', DataCentre::class);
return view('data_centres.create', ['dataCentre' => new DataCentre]);
}
public function store(DataCentreRequest $request)
{
$dataCentre = DataCentre::create($request->validated());
return redirect()->route('data-centres.show', $dataCentre)
->with('success', 'Pusat data berjaya didaftarkan.');
}
public function show(DataCentre $dataCentre)
{
$this->authorize('view', $dataCentre);
$dataCentre->load([
'currentConsultant',
'assignments.consultant',
'assignments.assignedBy',
'submissions.reportingCycle',
]);
$consultants = Consultant::aktif()->orderBy('nama_perunding')->get();
return view('data_centres.show', compact('dataCentre', 'consultants'));
}
public function edit(DataCentre $dataCentre)
{
$this->authorize('update', $dataCentre);
return view('data_centres.edit', compact('dataCentre'));
}
public function update(DataCentreRequest $request, DataCentre $dataCentre)
{
$this->authorize('update', $dataCentre);
$dataCentre->update($request->validated());
return redirect()->route('data-centres.show', $dataCentre)
->with('success', 'Maklumat pusat data dikemas kini.');
}
public function destroy(DataCentre $dataCentre)
{
$this->authorize('delete', $dataCentre);
$dataCentre->delete();
return redirect()->route('data-centres.index')
->with('success', 'Pusat data telah dipadam.');
}
/** Tugaskan / tukar perunding aktif untuk pusat data. */
public function assign(Request $request, DataCentre $dataCentre)
{
$this->authorize('assignConsultant', $dataCentre);
$data = $request->validate([
'consultant_id' => ['required', 'exists:consultants,id'],
'start_date' => ['nullable', 'date'],
'reason' => ['nullable', 'string', 'max:500'],
], [], ['consultant_id' => 'perunding', 'reason' => 'sebab']);
$consultant = Consultant::findOrFail($data['consultant_id']);
$this->assignmentService->assign(
$dataCentre,
$consultant,
auth()->user(),
$data['start_date'] ?? null,
$data['reason'] ?? null
);
return redirect()->route('data-centres.show', $dataCentre)
->with('success', 'Perunding berjaya ditugaskan. Penugasan terdahulu (jika ada) telah ditamatkan.');
}
public function endAssignment(Request $request, DataCentre $dataCentre)
{
$this->authorize('assignConsultant', $dataCentre);
$data = $request->validate(['reason' => ['nullable', 'string', 'max:500']]);
$this->assignmentService->endActive($dataCentre, auth()->user(), $data['reason'] ?? null);
return redirect()->route('data-centres.show', $dataCentre)
->with('success', 'Penugasan perunding aktif telah ditamatkan.');
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace App\Http\Controllers;
use App\Models\AppSetting;
use App\Models\Submission;
use App\Models\SubmissionDocument;
use App\Services\DocumentService;
use Illuminate\Http\Request;
class DocumentController extends Controller
{
public function __construct(protected DocumentService $documents) {}
/** Muat naik laporan PDF (perunding sahaja, serahan belum dikunci). */
public function store(Request $request, Submission $submission)
{
$this->authorize('update', $submission);
$maxKb = (int) AppSetting::get('max_upload_size_kb', config('mbip.max_upload_size_kb', 20480));
$request->validate([
'report' => ['required', 'file', 'mimes:pdf', 'mimetypes:application/pdf', "max:$maxKb"],
], [], ['report' => 'laporan PDF']);
$this->documents->store($submission, $request->file('report'), auth()->user());
return back()->with('success', 'Laporan PDF berjaya dimuat naik (versi baharu dicipta).');
}
/** Muat turun laporan — dilindungi oleh polisi. */
public function download(SubmissionDocument $document)
{
$this->authorize('view', $document->submission);
return $this->documents->download($document);
}
public function setActive(SubmissionDocument $document)
{
$this->authorize('update', $document->submission);
$this->documents->setActiveVersion($document->submission, $document);
return back()->with('success', 'Versi laporan aktif dikemas kini.');
}
}

View File

@@ -0,0 +1,67 @@
<?php
namespace App\Http\Controllers;
use App\Models\DataCentre;
use App\Models\ReportingCycle;
use App\Models\Submission;
use Illuminate\Http\Request;
class ReminderController extends Controller
{
public function index(Request $request)
{
abort_unless(auth()->user()->can('peringatan.lihat'), 403);
$cycles = ReportingCycle::orderByDesc('renewal_year')->get();
$cycleId = $request->input('cycle') ?: ReportingCycle::aktif()->orderByDesc('renewal_year')->value('id');
$cycle = $cycleId ? ReportingCycle::find($cycleId) : null;
$rows = collect();
if ($cycle) {
// Pusat data aktif yang mempunyai perunding aktif.
$query = DataCentre::query()->aktif()
->whereNotNull('current_consultant_id')
->with('currentConsultant');
// ID pusat data yang TELAH menghantar (bukan draf) untuk kitaran ini.
$submittedDcIds = Submission::where('reporting_cycle_id', $cycle->id)
->where('status', '!=', 'draf')
->pluck('data_centre_id')->all();
// Buang yang telah hantar — peraturan: keluar dari senarai peringatan selepas hantar.
$query->whereNotIn('id', $submittedDcIds);
if ($request->filled('consultant')) {
$query->where('current_consultant_id', $request->consultant);
}
if ($request->filled('data_centre')) {
$query->where('id', $request->data_centre);
}
$rows = $query->orderBy('nama_pusat_data')->get()->map(function ($dc) use ($cycle) {
// Adakah ada draf separa lengkap?
$draft = Submission::where('reporting_cycle_id', $cycle->id)
->where('data_centre_id', $dc->id)->first();
return (object) [
'data_centre' => $dc,
'consultant' => $dc->currentConsultant,
'status' => $draft ? $draft->status : 'belum_mula',
'is_overdue' => $cycle->isOverdue(),
'submission' => $draft,
];
});
// Penapis lampau tempoh sahaja.
if ($request->boolean('overdue')) {
$rows = $rows->filter(fn ($r) => $r->is_overdue)->values();
}
}
$consultants = \App\Models\Consultant::aktif()->orderBy('nama_perunding')->get();
return view('reminders.index', compact('rows', 'cycles', 'cycle', 'consultants'));
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace App\Http\Controllers;
use App\Models\ReportingCycle;
use App\Models\Submission;
use App\Models\SubmissionResult;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class ReportController extends Controller
{
public function index(Request $request)
{
abort_unless(auth()->user()->can('laporan.lihat'), 403);
$cycles = ReportingCycle::orderByDesc('renewal_year')->get();
$cycleId = $request->input('cycle');
$resultsQuery = SubmissionResult::query()
->join('submissions', 'submissions.id', '=', 'submission_results.submission_id')
->join('data_centres', 'data_centres.id', '=', 'submissions.data_centre_id')
->join('reporting_cycles', 'reporting_cycles.id', '=', 'submissions.reporting_cycle_id')
->whereNull('submissions.deleted_at');
if ($cycleId) {
$resultsQuery->where('submissions.reporting_cycle_id', $cycleId);
}
// Jadual hasil karbon mengikut pusat data + kitaran.
$rows = (clone $resultsQuery)
->select(
'data_centres.nama_pusat_data',
'reporting_cycles.reporting_year',
'submission_results.result_token',
'submission_results.value'
)
->orderBy('reporting_cycles.reporting_year')
->orderBy('data_centres.nama_pusat_data')
->get()
->groupBy(fn ($r) => $r->nama_pusat_data.'|'.$r->reporting_year);
// Trend jumlah karbon (A) mengikut tahun pelaporan.
$trend = (clone $resultsQuery)
->where('submission_results.result_token', 'A')
->select('reporting_cycles.reporting_year', DB::raw('SUM(submission_results.value) as jumlah'))
->groupBy('reporting_cycles.reporting_year')
->orderBy('reporting_cycles.reporting_year')
->pluck('jumlah', 'reporting_year');
// Pecahan status serahan.
$statusBreakdown = Submission::query()
->when($cycleId, fn ($q) => $q->where('reporting_cycle_id', $cycleId))
->select('status', DB::raw('count(*) as jumlah'))
->groupBy('status')->pluck('jumlah', 'status');
return view('reports.index', compact('cycles', 'cycleId', 'rows', 'trend', 'statusBreakdown'));
}
}

View File

@@ -0,0 +1,88 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\ReportingCycleRequest;
use App\Models\ChecklistTemplate;
use App\Models\ReportingCycle;
use App\Services\ReportingCycleService;
class ReportingCycleController extends Controller
{
public function __construct(protected ReportingCycleService $service) {}
public function index()
{
$this->authorize('viewAny', ReportingCycle::class);
$cycles = ReportingCycle::withCount('submissions')
->orderByDesc('renewal_year')->paginate(15);
return view('reporting_cycles.index', compact('cycles'));
}
public function create()
{
$this->authorize('create', ReportingCycle::class);
$templates = ChecklistTemplate::orderByDesc('is_active')->get();
return view('reporting_cycles.create', [
'cycle' => new ReportingCycle(['licence_period_year' => 1, 'status' => 'aktif']),
'templates' => $templates,
]);
}
public function store(ReportingCycleRequest $request)
{
$attrs = $this->service->buildAttributes(
(int) $request->renewal_year,
$request->cut_off_date
);
$cycle = ReportingCycle::create(array_merge($request->validated(), $attrs));
return redirect()->route('reporting-cycles.index')
->with('success', "Kitaran pembaharuan {$cycle->renewal_year} (pelaporan {$cycle->reporting_year}) dicipta.");
}
public function edit(ReportingCycle $reportingCycle)
{
$this->authorize('update', $reportingCycle);
$templates = ChecklistTemplate::orderByDesc('is_active')->get();
return view('reporting_cycles.edit', ['cycle' => $reportingCycle, 'templates' => $templates]);
}
public function update(ReportingCycleRequest $request, ReportingCycle $reportingCycle)
{
$this->authorize('update', $reportingCycle);
// Kira semula tahun pelaporan & tempoh jika tahun pembaharuan berubah.
$attrs = $this->service->buildAttributes(
(int) $request->renewal_year,
$request->cut_off_date
);
// Kekalkan templat pilihan pengguna jika diberi.
$attrs['checklist_template_id'] = $request->checklist_template_id ?? $attrs['checklist_template_id'];
$reportingCycle->update(array_merge($request->validated(), $attrs));
return redirect()->route('reporting-cycles.index')
->with('success', 'Kitaran pelaporan dikemas kini.');
}
public function destroy(ReportingCycle $reportingCycle)
{
$this->authorize('delete', $reportingCycle);
if ($reportingCycle->submissions()->exists()) {
return back()->with('error', 'Kitaran tidak boleh dipadam kerana telah mempunyai serahan.');
}
$reportingCycle->delete();
return redirect()->route('reporting-cycles.index')->with('success', 'Kitaran dipadam.');
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace App\Http\Controllers;
use App\Models\AppSetting;
use Illuminate\Http\Request;
class SettingController extends Controller
{
public function edit()
{
abort_unless(auth()->user()->can('tetapan.urus'), 403);
$settings = AppSetting::orderBy('group')->orderBy('key')->get()->groupBy('group');
return view('settings.edit', compact('settings'));
}
public function update(Request $request)
{
abort_unless(auth()->user()->can('tetapan.urus'), 403);
$values = $request->input('settings', []);
// Proses setiap tetapan. Kotak semak (boolean) yang tidak ditanda tidak
// dihantar oleh borang, jadi tetapan boolean dikendalikan secara eksplisit.
foreach (AppSetting::all() as $setting) {
if ($setting->type === 'boolean') {
$value = $request->boolean("settings.{$setting->key}") ? '1' : '0';
} elseif (array_key_exists($setting->key, $values)) {
$value = $values[$setting->key];
} else {
continue;
}
AppSetting::set($setting->key, $value);
}
activity('tetapan')->causedBy(auth()->user())->log('Tetapan aplikasi dikemas kini');
return back()->with('success', 'Tetapan aplikasi disimpan.');
}
}

View File

@@ -0,0 +1,143 @@
<?php
namespace App\Http\Controllers;
use App\Models\DataCentre;
use App\Models\ReportingCycle;
use App\Models\Submission;
use App\Services\SubmissionWorkflowService;
use Illuminate\Http\Request;
class SubmissionController extends Controller
{
public function __construct(protected SubmissionWorkflowService $workflow) {}
public function index(Request $request)
{
$this->authorize('viewAny', Submission::class);
$user = auth()->user();
$query = Submission::query()->with(['dataCentre', 'reportingCycle', 'consultant', 'assignedOfficer']);
// Perunding: hanya serahan sendiri.
if (! $user->can('serahan.lihat_semua')) {
$query->where('consultant_id', $user->consultant?->id ?? 0);
}
if ($request->filled('cycle')) {
$query->where('reporting_cycle_id', $request->cycle);
}
if ($request->filled('status')) {
$query->where('status', $request->status);
}
if ($request->filled('consultant') && $user->can('serahan.lihat_semua')) {
$query->where('consultant_id', $request->consultant);
}
$submissions = $query->latest()->paginate(15)->withQueryString();
$cycles = ReportingCycle::orderByDesc('renewal_year')->get();
return view('submissions.index', compact('submissions', 'cycles'));
}
/** Borang pilih pusat data + kitaran untuk memulakan serahan. */
public function create(Request $request)
{
$this->authorize('create', Submission::class);
$consultant = auth()->user()->consultant;
abort_unless($consultant, 403, 'Akaun anda tiada profil perunding.');
$dataCentres = DataCentre::where('current_consultant_id', $consultant->id)->aktif()->get();
$cycles = ReportingCycle::aktif()->orderByDesc('renewal_year')->get();
return view('submissions.create', compact('dataCentres', 'cycles'));
}
public function store(Request $request)
{
$this->authorize('create', Submission::class);
$consultant = auth()->user()->consultant;
abort_unless($consultant, 403);
$data = $request->validate([
'data_centre_id' => ['required', 'exists:data_centres,id'],
'reporting_cycle_id' => ['required', 'exists:reporting_cycles,id'],
], [], ['data_centre_id' => 'pusat data', 'reporting_cycle_id' => 'kitaran pelaporan']);
$dataCentre = DataCentre::findOrFail($data['data_centre_id']);
// Pastikan pusat data ini milik perunding.
abort_unless((int) $dataCentre->current_consultant_id === (int) $consultant->id, 403,
'Anda tidak ditugaskan untuk pusat data ini.');
$cycle = ReportingCycle::findOrFail($data['reporting_cycle_id']);
$submission = $this->workflow->findOrCreateDraft($dataCentre, $cycle);
return redirect()->route('submissions.edit', $submission)
->with('success', 'Serahan draf disediakan. Sila lengkapkan senarai semak.');
}
/** Borang pengisian senarai semak (perunding). */
public function edit(Submission $submission)
{
$this->authorize('update', $submission);
$submission->load([
'dataCentre', 'reportingCycle', 'checklistTemplate',
'answers', 'documents', 'correctionRequests',
]);
$template = $submission->checklistTemplate;
$template?->load(['sections.scopes', 'sections.items.scope', 'formulas']);
$answers = $submission->answers->keyBy('checklist_item_id');
return view('submissions.fill', compact('submission', 'template', 'answers'));
}
public function update(Request $request, Submission $submission)
{
$this->authorize('update', $submission);
$answers = $request->input('answers', []);
$this->workflow->saveAnswers($submission, $answers);
return back()->with('success', 'Draf senarai semak disimpan.');
}
public function submit(Request $request, Submission $submission)
{
$this->authorize('update', $submission);
// Simpan dahulu jawapan terkini jika dihantar bersama.
if ($request->has('answers')) {
$this->workflow->saveAnswers($submission, $request->input('answers', []));
}
$this->workflow->submit($submission, auth()->user());
return redirect()->route('submissions.show', $submission)
->with('success', 'Serahan berjaya dihantar kepada JPP.');
}
public function show(Submission $submission)
{
$this->authorize('view', $submission);
$submission->load([
'dataCentre', 'reportingCycle', 'consultant', 'assignedOfficer',
'checklistTemplate.sections.scopes', 'checklistTemplate.sections.items.scope',
'answers', 'results', 'documents.uploadedBy',
'statusHistories.changedBy', 'correctionRequests.item', 'correctionRequests.createdBy',
'comments.user',
]);
$template = $submission->checklistTemplate;
$answers = $submission->answers->keyBy('checklist_item_id');
$officers = \App\Models\User::role(['Pegawai JPP', 'Penolong Pegawai', 'Kerani', 'JPP Admin'])->get();
return view('submissions.show', compact('submission', 'template', 'answers', 'officers'));
}
}

View File

@@ -0,0 +1,122 @@
<?php
namespace App\Http\Controllers;
use App\Models\Submission;
use App\Models\SubmissionAnswer;
use App\Services\SubmissionWorkflowService;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
class SubmissionReviewController extends Controller
{
public function __construct(protected SubmissionWorkflowService $workflow) {}
/** Tukar status serahan. Status 'selesai' & 'pembetulan_perunding' ada syarat tambahan. */
public function updateStatus(Request $request, Submission $submission)
{
$this->authorize('review', $submission);
$data = $request->validate([
'status' => ['required', Rule::in(array_keys(Submission::STATUSES))],
'notes' => ['nullable', 'string', 'max:1000'],
// Wajib jika status = selesai: tarikh hardcopy dihantar ke Bahagian Kawalan Perancangan.
'hardcopy_sent_date' => ['nullable', 'required_if:status,selesai', 'date'],
], [], ['hardcopy_sent_date' => 'tarikh hardcopy dihantar ke Bahagian Kawalan Perancangan']);
// Status 'pembetulan_perunding' mesti melalui panel permohonan pembetulan.
if ($data['status'] === 'pembetulan_perunding' && ! $submission->correctionRequests()->where('status', 'terbuka')->exists()) {
return back()->with('error', 'Sila tambah sekurang-kurangnya satu permohonan pembetulan (no muka surat, lokasi & penerangan) sebelum menetapkan status Pembetulan Perunding.');
}
$extra = [];
if ($data['status'] === 'selesai') {
$extra['hardcopy_sent_date'] = $data['hardcopy_sent_date'];
}
$this->workflow->changeStatus($submission, $data['status'], auth()->user(), $data['notes'] ?? null, $extra);
return back()->with('success', 'Status serahan dikemas kini.');
}
public function assignOfficer(Request $request, Submission $submission)
{
$this->authorize('review', $submission);
$data = $request->validate(['assigned_officer_id' => ['nullable', 'exists:users,id']]);
$submission->update(['assigned_officer_id' => $data['assigned_officer_id'] ?? null]);
activity('serahan')->performedOn($submission)->causedBy(auth()->user())
->log('Pegawai bertanggungjawab dikemas kini');
return back()->with('success', 'Pegawai bertanggungjawab dikemas kini.');
}
public function lock(Request $request, Submission $submission)
{
$this->authorize('lock', $submission);
$data = $request->validate(['lock_reason' => ['nullable', 'string', 'max:500']]);
$this->workflow->lock($submission, auth()->user(), $data['lock_reason'] ?? null);
return back()->with('success', 'Serahan telah dikunci. Perunding tidak boleh mengubah sehingga dibuka semula.');
}
public function unlock(Request $request, Submission $submission)
{
$this->authorize('lock', $submission);
$data = $request->validate([
'lock_reason' => ['required', 'string', 'max:500'],
], [], ['lock_reason' => 'sebab buka kunci']);
$this->workflow->unlock($submission, auth()->user(), $data['lock_reason']);
return back()->with('success', 'Serahan dibuka semula untuk pembetulan oleh perunding.');
}
public function markHardcopy(Request $request, Submission $submission)
{
$this->authorize('markHardcopy', $submission);
$data = $request->validate([
'hardcopy_received' => ['required', 'boolean'],
'hardcopy_received_date' => ['nullable', 'required_if:hardcopy_received,1', 'date'],
], [], ['hardcopy_received_date' => 'tarikh terima salinan keras']);
$submission->update([
'hardcopy_received' => $request->boolean('hardcopy_received'),
'hardcopy_received_date' => $request->boolean('hardcopy_received') ? $data['hardcopy_received_date'] : null,
]);
activity('serahan')->performedOn($submission)->causedBy(auth()->user())
->log('Status penerimaan salinan keras dikemas kini');
return back()->with('success', 'Status salinan keras dikemas kini.');
}
public function reviewNote(Request $request, Submission $submission)
{
$this->authorize('review', $submission);
$data = $request->validate(['review_notes' => ['nullable', 'string', 'max:2000']]);
$submission->update(['review_notes' => $data['review_notes'] ?? null]);
return back()->with('success', 'Nota semakan disimpan.');
}
/** Pegawai mengesahkan / catat nota bagi satu jawapan item. */
public function reviewAnswer(Request $request, SubmissionAnswer $answer)
{
$this->authorize('review', $answer->submission);
$data = $request->validate([
'validation_status' => ['required', 'in:belum_semak,sah,tidak_sah'],
'officer_note' => ['nullable', 'string', 'max:1000'],
]);
$answer->update($data);
return back()->with('success', 'Semakan item dikemas kini.');
}
}

View File

@@ -0,0 +1,108 @@
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\Password;
use Spatie\Permission\Models\Role;
class UserController extends Controller
{
public function index(Request $request)
{
abort_unless(auth()->user()->can('pengguna.urus'), 403);
$users = User::with('roles')
->when($request->filled('cari'), fn ($q) => $q->where('name', 'like', '%'.$request->cari.'%')->orWhere('email', 'like', '%'.$request->cari.'%'))
->orderBy('name')->paginate(15)->withQueryString();
return view('users.index', compact('users'));
}
public function create()
{
abort_unless(auth()->user()->can('pengguna.urus'), 403);
return view('users.create', ['user' => new User, 'roles' => Role::orderBy('name')->get()]);
}
public function store(Request $request)
{
abort_unless(auth()->user()->can('pengguna.urus'), 403);
$data = $request->validate([
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'email', 'max:255', Rule::unique('users', 'email')],
'phone' => ['nullable', 'string', 'max:50'],
'jawatan' => ['nullable', 'string', 'max:255'],
'password' => ['required', 'confirmed', Password::min(8)],
'role' => ['required', 'exists:roles,name'],
'is_active' => ['nullable', 'boolean'],
]);
$user = User::create([
'name' => $data['name'],
'email' => $data['email'],
'phone' => $data['phone'] ?? null,
'jawatan' => $data['jawatan'] ?? null,
'password' => Hash::make($data['password']),
'is_active' => $request->boolean('is_active'),
'email_verified_at' => now(),
]);
$user->syncRoles([$data['role']]);
return redirect()->route('users.index')->with('success', 'Pengguna dicipta.');
}
public function edit(User $user)
{
abort_unless(auth()->user()->can('pengguna.urus'), 403);
return view('users.edit', ['user' => $user, 'roles' => Role::orderBy('name')->get()]);
}
public function update(Request $request, User $user)
{
abort_unless(auth()->user()->can('pengguna.urus'), 403);
$data = $request->validate([
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'email', 'max:255', Rule::unique('users', 'email')->ignore($user->id)],
'phone' => ['nullable', 'string', 'max:50'],
'jawatan' => ['nullable', 'string', 'max:255'],
'password' => ['nullable', 'confirmed', Password::min(8)],
'role' => ['required', 'exists:roles,name'],
'is_active' => ['nullable', 'boolean'],
]);
$user->update([
'name' => $data['name'],
'email' => $data['email'],
'phone' => $data['phone'] ?? null,
'jawatan' => $data['jawatan'] ?? null,
'is_active' => $request->boolean('is_active'),
]);
if (! empty($data['password'])) {
$user->update(['password' => Hash::make($data['password'])]);
}
$user->syncRoles([$data['role']]);
return redirect()->route('users.index')->with('success', 'Pengguna dikemas kini.');
}
public function destroy(User $user)
{
abort_unless(auth()->user()->can('pengguna.urus'), 403);
abort_if($user->id === auth()->id(), 403, 'Anda tidak boleh memadam akaun sendiri.');
$user->delete();
return redirect()->route('users.index')->with('success', 'Pengguna dipadam.');
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class ConsultantRequest extends FormRequest
{
public function authorize(): bool
{
return $this->user()->can('perunding.urus');
}
public function rules(): array
{
$consultantId = $this->route('consultant')?->id;
return [
'nama_perunding' => ['required', 'string', 'max:255'],
'no_pendaftaran_syarikat' => ['nullable', 'string', 'max:100'],
'alamat' => ['nullable', 'string', 'max:1000'],
'emel' => ['nullable', 'email', 'max:255'],
'telefon' => ['nullable', 'string', 'max:50'],
'aktif' => ['required', 'boolean'],
// Maklumat akaun pengguna perunding
'buat_akaun' => ['nullable', 'boolean'],
'user_name' => ['nullable', 'required_if:buat_akaun,1', 'string', 'max:255'],
'user_email' => [
'nullable', 'required_if:buat_akaun,1', 'email', 'max:255',
Rule::unique('users', 'email')->ignore($this->route('consultant')?->user_id),
],
'user_password' => ['nullable', 'required_if:buat_akaun,1', 'string', 'min:8'],
];
}
public function attributes(): array
{
return [
'nama_perunding' => 'nama perunding',
'no_pendaftaran_syarikat' => 'no pendaftaran syarikat',
'emel' => 'emel',
'telefon' => 'telefon',
'user_name' => 'nama pengguna',
'user_email' => 'emel akaun',
'user_password' => 'kata laluan akaun',
];
}
protected function prepareForValidation(): void
{
$this->merge([
'aktif' => $this->boolean('aktif'),
'buat_akaun' => $this->boolean('buat_akaun'),
]);
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class DataCentreRequest extends FormRequest
{
public function authorize(): bool
{
return $this->user()->can('pusat_data.urus');
}
public function rules(): array
{
return [
'nama_pusat_data' => ['required', 'string', 'max:255'],
'tajuk_permohonan' => ['nullable', 'string', 'max:255'],
'lokasi_pusat_data' => ['nullable', 'string', 'max:255'],
'alamat' => ['nullable', 'string', 'max:1000'],
'keluasan_tapak' => ['nullable', 'numeric', 'min:0'],
'it_load' => ['nullable', 'numeric', 'min:0'],
'status' => ['required', 'in:aktif,tidak_aktif'],
];
}
public function attributes(): array
{
return [
'nama_pusat_data' => 'nama pusat data',
'tajuk_permohonan' => 'tajuk permohonan',
'lokasi_pusat_data' => 'lokasi pusat data',
'keluasan_tapak' => 'keluasan tapak',
'it_load' => 'kapasiti IT Load',
];
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class ReportingCycleRequest extends FormRequest
{
public function authorize(): bool
{
return $this->user()->can('kitaran.urus');
}
public function rules(): array
{
$cycleId = $this->route('reporting_cycle')?->id;
return [
'renewal_year' => [
'required', 'integer', 'min:2000', 'max:2100',
Rule::unique('reporting_cycles', 'renewal_year')->ignore($cycleId),
],
'cut_off_date' => ['nullable', 'date'],
'licence_period_year' => ['required', 'integer', 'min:1', 'max:10'],
'checklist_template_id' => ['nullable', 'exists:checklist_templates,id'],
'status' => ['required', 'in:aktif,tutup'],
'catatan' => ['nullable', 'string', 'max:1000'],
];
}
public function attributes(): array
{
return [
'renewal_year' => 'tahun pembaharuan',
'cut_off_date' => 'tarikh tutup',
'licence_period_year' => 'tempoh lesen (tahun)',
];
}
}