Update Role fasilitator, kordinator

This commit is contained in:
2026-11-04 14:24:51 +07:00
parent b5b046ed86
commit 41da3a71ee
48 changed files with 586 additions and 164 deletions
+4 -2
View File
@@ -24,7 +24,7 @@ Baca seluruhnya sebelum membuat, mengubah, atau merefaktor kode.
| Auth web | Session |
| Auth API | Laravel Sanctum (token) |
| Dokumentasi API | dedoc/scramble |
| Role | `app/Enums/Role.php` (atau lokasi Role yang sudah ada): `super_admin`, `admin`, `fasilitator`, `petani`, `offtaker` |
| Role | `app/Enums/Role.php`: `super_admin`, `admin` (admin program), `koordinator` (per komoditas), `enumerator` (penginput data petani), `fasilitator` (pemateri workshop), `petani`, `offtaker` |
---
@@ -452,7 +452,7 @@ Aturan turunannya:
- Kelompokkan per domain dengan `Route::middleware(...)->group()` dan komentar judul domain.
- Nested resource memakai `->shallow()`. Maksimal **dua tingkat** di URL (`/framework/{framework}/transformasi`). Hierarki lebih dalam (Framework → Transformasi → Aktivitas → Target) dipecah menjadi beberapa nested shallow resource berpasangan.
- **Satu route hanya didaftarkan satu kali.** Sebelum menambah route, cari dulu apakah sudah ada (`php artisan route:list --name=...`).
- Setelah login: `fasilitator` → Dashboard Survei Petani; role lain → Dashboard Karakteristik Petani. Jangan mengubah logika ini tanpa izin.
- Setelah login: `enumerator` → Dashboard Survei Petani; `fasilitator` → Profil akun (belum ada menu khusus); role lain → Dashboard Karakteristik Petani. Jangan mengubah logika ini tanpa izin.
---
@@ -544,6 +544,8 @@ Kunci flash: `success`, `error`, `warning`, `info`.
## 9. Otorisasi
- Hak akses berdasarkan role pada `Role` yang sudah ada. Ikuti mekanisme pembatasan role yang sudah dipakai (middleware/policy/gate yang ada); jangan membuat sistem permission baru.
- Penginput data petani adalah role `enumerator` (bukan `fasilitator`). `fasilitator` khusus pemateri workshop.
- Semua route `petani/...` dibungkus middleware `akses.petani` (`BatasiAksesPetani`): hanya `super_admin`, `admin`, `koordinator`, `enumerator`; koordinator hanya boleh mengakses petani dengan komoditas utama yang ditugaskan (`koordinator_komoditas`, cek lewat `User::bolehAksesPetani()`). Route tab petani baru wajib diletakkan di dalam grup ini.
- Menu sidebar hanya menampilkan item yang boleh diakses role pengguna, dan route-nya juga dilindungi di sisi server. Menyembunyikan menu saja tidak cukup.
---
+16 -1
View File
@@ -6,7 +6,22 @@ enum Role: string
{
case SUPER_ADMIN = 'super_admin';
case ADMIN = 'admin'; // admin program
case FASILITATOR = 'fasilitator';
case KOORDINATOR = 'koordinator'; // koordinator per komoditas
case ENUMERATOR = 'enumerator'; // penginput data petani di lapangan
case FASILITATOR = 'fasilitator'; // pemateri workshop
case PETANI = 'petani';
case OFFTAKER = 'offtaker';
public function label(): string
{
return match ($this) {
self::SUPER_ADMIN => 'Super Admin',
self::ADMIN => 'Admin',
self::KOORDINATOR => 'Koordinator',
self::ENUMERATOR => 'Enumerator',
self::FASILITATOR => 'Fasilitator',
self::PETANI => 'Petani',
self::OFFTAKER => 'Offtaker',
};
}
}
@@ -20,7 +20,7 @@ class FormBasisDataController extends Controller
*/
public function index(Request $request, string $tabelId): JsonResponse
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403);
}
@@ -20,7 +20,7 @@ class TabelBasisDataController extends Controller
*/
public function index(Request $request): AnonymousResourceCollection
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403);
}
@@ -50,7 +50,7 @@ class TabelBasisDataController extends Controller
*/
public function show(Request $request, string $id): JsonResponse
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403);
}
@@ -19,7 +19,7 @@ class ValueBasisDataController extends Controller
public function index(Request $request, string $tabelId): JsonResponse
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403);
}
@@ -213,7 +213,7 @@ class ValueBasisDataController extends Controller
public function store(Request $request, string $tabelId): JsonResponse
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403);
}
@@ -278,7 +278,7 @@ class ValueBasisDataController extends Controller
public function update(Request $request, string $tabelId, string $id): JsonResponse
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403);
}
@@ -354,7 +354,7 @@ class ValueBasisDataController extends Controller
public function destroy(Request $request, string $tabelId, string $id): JsonResponse
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403);
}
@@ -20,7 +20,7 @@ class JenisKomoditasController extends Controller
*/
public function index(Request $request): AnonymousResourceCollection
{
if (!Gate::any(['petani', 'fasilitator', 'super_admin'])) {
if (!Gate::any(['petani', 'enumerator', 'super_admin'])) {
abort(403);
}
@@ -63,7 +63,7 @@ class JenisKomoditasController extends Controller
*/
public function store(Request $request): JsonResponse
{
if (!Gate::any(['petani', 'fasilitator', 'super_admin'])) {
if (!Gate::any(['petani', 'enumerator', 'super_admin'])) {
abort(403);
}
@@ -85,7 +85,7 @@ class JenisKomoditasController extends Controller
*/
public function show(string $id): JsonResponse
{
if (!Gate::any(['petani', 'fasilitator', 'super_admin'])) {
if (!Gate::any(['petani', 'enumerator', 'super_admin'])) {
abort(403);
}
@@ -107,7 +107,7 @@ class JenisKomoditasController extends Controller
*/
public function update(Request $request, string $id): JsonResponse
{
if (!Gate::any(['petani', 'fasilitator', 'super_admin'])) {
if (!Gate::any(['petani', 'enumerator', 'super_admin'])) {
abort(403);
}
@@ -129,7 +129,7 @@ class JenisKomoditasController extends Controller
*/
public function destroy(string $id): JsonResponse
{
if (!Gate::any(['petani', 'fasilitator', 'super_admin'])) {
if (!Gate::any(['petani', 'enumerator', 'super_admin'])) {
abort(403);
}
@@ -13,7 +13,7 @@ class DistributionController extends Controller
{
public function index(): JsonResponse
{
if (!Gate::any(['super_admin', 'petani','fasilitator', 'offtaker'])) {
if (!Gate::any(['super_admin', 'petani','enumerator', 'offtaker'])) {
abort(403);
}
@@ -35,7 +35,7 @@ class DistributionController extends Controller
public function store(Request $request): JsonResponse
{
if (!Gate::any(['super_admin', 'petani','fasilitator', 'offtaker'])) {
if (!Gate::any(['super_admin', 'petani','enumerator', 'offtaker'])) {
abort(403);
}
@@ -60,7 +60,7 @@ class DistributionController extends Controller
public function update(Request $request, string $id): JsonResponse
{
if (!Gate::any(['super_admin', 'petani','fasilitator', 'offtaker'])) {
if (!Gate::any(['super_admin', 'petani','enumerator', 'offtaker'])) {
abort(403);
}
@@ -86,7 +86,7 @@ class DistributionController extends Controller
public function batchUpsert(Request $request): JsonResponse
{
if (!Gate::any(['super_admin', 'petani','fasilitator', 'offtaker'])) {
if (!Gate::any(['super_admin', 'petani','enumerator', 'offtaker'])) {
abort(403);
}
@@ -21,7 +21,7 @@ class LahanController extends Controller
*/
public function index(Request $request): AnonymousResourceCollection
{
if (!Gate::any(['petani', 'fasilitator','super_admin'])) {
if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403);
}
@@ -77,7 +77,7 @@ class LahanController extends Controller
*/
public function store(Request $request): JsonResponse
{
if (!Gate::any(['petani', 'fasilitator','super_admin'])) {
if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403);
}
@@ -91,7 +91,7 @@ class LahanController extends Controller
$request->merge(['profile_id' => (int) $request->profile_id]);
}
$profileRule = $user->role === 'fasilitator' ? ['required', 'numeric', 'exists:profiles,id'] : ['nullable'];
$profileRule = $user->role === 'enumerator' ? ['required', 'numeric', 'exists:profiles,id'] : ['nullable'];
$validated = $request->validate([
'nama' => ['required', 'string', 'max:255'],
@@ -105,11 +105,11 @@ class LahanController extends Controller
'foto.*' => ['image', 'mimes:jpeg,png,jpg', 'max:5120'], // Max 5MB
]);
$targetProfileId = $user->role === 'fasilitator'
$targetProfileId = $user->role === 'enumerator'
? $validated['profile_id']
: $profile->id;
if ($user->role === 'fasilitator') {
if ($user->role === 'enumerator') {
$targetProfile = Profile::findOrFail($targetProfileId);
if ($targetProfile->desa_kelurahan_id !== $profile->desa_kelurahan_id) {
@@ -117,7 +117,7 @@ class LahanController extends Controller
}
}
$desaKelurahanId = $user->role === 'fasilitator'
$desaKelurahanId = $user->role === 'enumerator'
? $profile->desa_kelurahan_id
: ($validated['desa_kelurahan_id'] ?? $profile->desa_kelurahan_id);
@@ -149,14 +149,14 @@ class LahanController extends Controller
*/
public function show(string $id): JsonResponse
{
if (!Gate::any(['petani', 'fasilitator','super_admin'])) {
if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403);
}
$lahan = Lahan::with('desaKelurahan')->findOrFail($id);
if ($lahan->profile_id !== Auth::user()->profile->id && Auth::user()->role === 'petani') {
return response()->json(['message' => 'Unauthorized'], 403);
} else if (Auth::user()->role === 'fasilitator' && $lahan->desa_kelurahan_id !== Auth::user()->profile->desa_kelurahan_id) {
} else if (Auth::user()->role === 'enumerator' && $lahan->desa_kelurahan_id !== Auth::user()->profile->desa_kelurahan_id) {
return response()->json(['message' => 'Unauthorized'], 403);
}
@@ -168,14 +168,14 @@ class LahanController extends Controller
*/
public function update(Request $request, string $id): JsonResponse
{
if (!Gate::any(['petani', 'fasilitator','super_admin'])) {
if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403);
}
$lahan = Lahan::findOrFail($id);
if ($lahan->profile_id !== Auth::user()->profile->id && Auth::user()->role === 'petani') {
return response()->json(['message' => 'Unauthorized'], 403);
} else if (Auth::user()->role === 'fasilitator' && $lahan->desa_kelurahan_id !== Auth::user()->profile->desa_kelurahan_id) {
} else if (Auth::user()->role === 'enumerator' && $lahan->desa_kelurahan_id !== Auth::user()->profile->desa_kelurahan_id) {
return response()->json(['message' => 'Unauthorized'], 403);
}
@@ -215,14 +215,14 @@ class LahanController extends Controller
*/
public function destroy(string $id): JsonResponse
{
if (!Gate::any(['petani', 'fasilitator','super_admin'])) {
if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403);
}
$lahan = Lahan::findOrFail($id);
if ($lahan->profile_id !== Auth::user()->profile->id && Auth::user()->role === 'petani') {
return response()->json(['message' => 'Unauthorized'], 403);
} else if (Auth::user()->role === 'fasilitator' && $lahan->desa_kelurahan_id !== Auth::user()->profile->desa_kelurahan_id) {
} else if (Auth::user()->role === 'enumerator' && $lahan->desa_kelurahan_id !== Auth::user()->profile->desa_kelurahan_id) {
return response()->json(['message' => 'Unauthorized'], 403);
}
@@ -233,7 +233,7 @@ class LahanController extends Controller
public function batchUpsert(Request $request): JsonResponse
{
if (!Gate::any(['petani', 'fasilitator','super_admin'])) {
if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403);
}
@@ -12,7 +12,7 @@ class OfftakerController extends Controller
{
public function index(Request $request): JsonResponse
{
if (!Gate::any(['super_admin', 'petani', 'fasilitator', 'admin','offtaker'])) {
if (!Gate::any(['super_admin', 'petani', 'enumerator', 'admin','offtaker'])) {
abort(403, 'Unauthorized');
}
@@ -29,7 +29,7 @@ class OfftakerController extends Controller
public function store(Request $request): JsonResponse
{
if (!Gate::any(['super_admin', 'petani', 'fasilitator', 'admin','offtaker'])) {
if (!Gate::any(['super_admin', 'petani', 'enumerator', 'admin','offtaker'])) {
abort(403, 'Unauthorized');
}
@@ -58,7 +58,7 @@ class OfftakerController extends Controller
public function show(string $id): JsonResponse
{
if (!Gate::any(['super_admin', 'petani', 'fasilitator', 'admin','offtaker'])) {
if (!Gate::any(['super_admin', 'petani', 'enumerator', 'admin','offtaker'])) {
abort(403, 'Unauthorized');
}
$offtaker = Offtaker::findOrFail($id);
@@ -67,7 +67,7 @@ class OfftakerController extends Controller
public function update(Request $request, string $id): JsonResponse
{
if (!Gate::any(['super_admin', 'petani', 'fasilitator', 'admin','offtaker'])) {
if (!Gate::any(['super_admin', 'petani', 'enumerator', 'admin','offtaker'])) {
abort(403, 'Unauthorized');
}
$offtaker = Offtaker::findOrFail($id);
@@ -87,7 +87,7 @@ class OfftakerController extends Controller
public function destroy(string $id): JsonResponse
{
if (!Gate::any(['super_admin', 'petani', 'fasilitator', 'admin','offtaker'])) {
if (!Gate::any(['super_admin', 'petani', 'enumerator', 'admin','offtaker'])) {
abort(403, 'Unauthorized');
}
Offtaker::findOrFail($id)->delete();
@@ -100,7 +100,7 @@ class OfftakerController extends Controller
*/
public function batchUpsert(Request $request): JsonResponse
{
if (!Gate::any(['super_admin', 'petani', 'fasilitator', 'admin','offtaker'])) {
if (!Gate::any(['super_admin', 'petani', 'enumerator', 'admin','offtaker'])) {
abort(403, 'Unauthorized');
}
@@ -15,7 +15,7 @@ class PanenController extends Controller
{
public function index(Request $request): JsonResponse
{
if (!Gate::any(['petani', 'fasilitator','super_admin'])) {
if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403);
}
@@ -30,7 +30,7 @@ class PanenController extends Controller
public function store(Request $request): JsonResponse
{
if (!Gate::any(['petani', 'fasilitator', 'super_admin'])) {
if (!Gate::any(['petani', 'enumerator', 'super_admin'])) {
abort(403);
}
@@ -87,7 +87,7 @@ class PanenController extends Controller
public function show(string $id): JsonResponse
{
if (!Gate::any(['petani', 'fasilitator','super_admin'])) {
if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403);
}
$panen = Panen::findOrFail($id);
@@ -96,7 +96,7 @@ class PanenController extends Controller
public function update(Request $request, string $id): JsonResponse
{
if (!Gate::any(['petani', 'fasilitator','super_admin'])) {
if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403);
}
$panen = Panen::findOrFail($id);
@@ -114,7 +114,7 @@ class PanenController extends Controller
public function destroy(string $id): JsonResponse
{
Gate::authorize(['petani', 'fasilitator']);
Gate::authorize(['petani', 'enumerator']);
Panen::findOrFail($id)->delete();
return response()->json(null, 204);
}
@@ -124,7 +124,7 @@ class PanenController extends Controller
*/
public function batchUpsert(Request $request): JsonResponse
{
if (Gate::authorize(['petani', 'fasilitator','super_admin'])) {
if (Gate::authorize(['petani', 'enumerator','super_admin'])) {
abort(403);
}
$profileId = Auth::user()->profile->id;
@@ -14,7 +14,7 @@ class PanenController01 extends Controller
{
public function index(Request $request): JsonResponse
{
if (!Gate::any(['petani', 'fasilitator','super_admin'])) {
if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403);
}
@@ -29,7 +29,7 @@ class PanenController01 extends Controller
public function store(Request $request): JsonResponse
{
if (!Gate::any(['petani', 'fasilitator','super_admin'])) {
if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403);
}
@@ -58,7 +58,7 @@ class PanenController01 extends Controller
public function show(string $id): JsonResponse
{
if (!Gate::any(['petani', 'fasilitator','super_admin'])) {
if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403);
}
$panen = Panen::findOrFail($id);
@@ -67,7 +67,7 @@ class PanenController01 extends Controller
public function update(Request $request, string $id): JsonResponse
{
if (!Gate::any(['petani', 'fasilitator','super_admin'])) {
if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403);
}
$panen = Panen::findOrFail($id);
@@ -85,7 +85,7 @@ class PanenController01 extends Controller
public function destroy(string $id): JsonResponse
{
Gate::authorize(['petani', 'fasilitator']);
Gate::authorize(['petani', 'enumerator']);
Panen::findOrFail($id)->delete();
return response()->json(null, 204);
}
@@ -95,7 +95,7 @@ class PanenController01 extends Controller
*/
public function batchUpsert(Request $request): JsonResponse
{
if (Gate::authorize(['petani', 'fasilitator','super_admin'])) {
if (Gate::authorize(['petani', 'enumerator','super_admin'])) {
abort(403);
}
$profileId = Auth::user()->profile->id;
@@ -23,7 +23,7 @@ class ProfileController extends Controller
*/
public function index(Request $request)
{
if(!Gate::any(['fasilitator', 'super_admin'])) {
if(!Gate::any(['enumerator', 'super_admin'])) {
abort(403);
}
@@ -34,7 +34,7 @@ class ProfileController extends Controller
$profileId = $user->role === 'petani' ? $profile->id : $requestID;
$desaKelurahanId = null;
if ($profile->role === 'fasilitator' || $profile->role === 'super_admin') {
if ($profile->role === 'enumerator' || $profile->role === 'super_admin') {
$desaKelurahanId = $profile->desa_kelurahan_id;
}
@@ -95,7 +95,7 @@ class ProfileController extends Controller
*/
public function store(Request $request)
{
if(!Gate::any(['fasilitator','super_admin'])){
if(!Gate::any(['enumerator','super_admin'])){
abort(403);
}
@@ -139,7 +139,7 @@ class ProfileController extends Controller
public function show(string $id): JSONResponse
{
// attempt to access profile, only for allowed roles
if (!Gate::any(['petani', 'fasilitator', 'offtaker', 'super_admin'])) {
if (!Gate::any(['petani', 'enumerator', 'offtaker', 'super_admin'])) {
abort(403);
}
@@ -166,7 +166,7 @@ class ProfileController extends Controller
*/
public function update(Request $request, string $id): JSONResponse
{
if (!Gate::any(['petani', 'fasilitator', 'offtaker', 'super_admin'])) {
if (!Gate::any(['petani', 'enumerator', 'offtaker', 'super_admin'])) {
abort(403);
}
@@ -177,7 +177,7 @@ class ProfileController extends Controller
$profile = Profile::findOrFail($id);
if ($user->role === 'fasilitator' && $profile->desa_kelurahan_id != $user->profile->desa_kelurahan_id) {
if ($user->role === 'enumerator' && $profile->desa_kelurahan_id != $user->profile->desa_kelurahan_id) {
abort(403, 'Unauthorized');
}
@@ -239,7 +239,7 @@ class ProfileController extends Controller
public function downloadFile(string $id, string $type): JsonResponse | BinaryFileResponse
{
if (!Gate::any(['petani', 'fasilitator', 'super_admin'])) {
if (!Gate::any(['petani', 'enumerator', 'super_admin'])) {
abort(403);
}
@@ -260,7 +260,7 @@ class ProfileController extends Controller
public function verify(Request $request, string $id): JSONResponse
{
if (!Gate::any(['fasilitator'])) {
if (!Gate::any(['enumerator'])) {
abort(403);
}
@@ -22,7 +22,7 @@ class TanamanController extends Controller
*/
public function index(Request $request): AnonymousResourceCollection
{
if (!Gate::any(['petani', 'fasilitator','super_admin'])) {
if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403);
}
$user = Auth::user();
@@ -91,7 +91,7 @@ class TanamanController extends Controller
*/
public function store(Request $request): JsonResponse
{
if (!Gate::any(['petani', 'fasilitator','super_admin'])) {
if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403);
}
@@ -135,7 +135,7 @@ class TanamanController extends Controller
*/
public function show(string $id): JsonResponse
{
if (!Gate::any(['petani', 'fasilitator','super_admin'])) {
if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403);
}
@@ -160,7 +160,7 @@ class TanamanController extends Controller
*/
public function update(Request $request, string $id): JsonResponse
{
if (!Gate::any(['petani', 'fasilitator','super_admin'])) {
if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403);
}
$tanaman = Tanaman::findOrFail($id);
@@ -195,7 +195,7 @@ class TanamanController extends Controller
*/
public function destroy(string $id): JsonResponse
{
if (!Gate::any(['petani', 'fasilitator','super_admin'])) {
if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403);
}
$tanaman = Tanaman::findOrFail($id);
@@ -22,7 +22,7 @@ class TanamanController extends Controller
*/
public function index(Request $request): AnonymousResourceCollection
{
if (!Gate::any(['petani', 'fasilitator','super_admin'])) {
if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403);
}
$user = Auth::user();
@@ -91,7 +91,7 @@ class TanamanController extends Controller
*/
public function store(Request $request): JsonResponse
{
if (!Gate::any(['petani', 'fasilitator','super_admin'])) {
if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403);
}
@@ -141,7 +141,7 @@ class TanamanController extends Controller
*/
public function show(string $id): JsonResponse
{
if (!Gate::any(['petani', 'fasilitator','super_admin'])) {
if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403);
}
@@ -166,7 +166,7 @@ class TanamanController extends Controller
*/
public function update(Request $request, string $id): JsonResponse
{
if (!Gate::any(['petani', 'fasilitator','super_admin'])) {
if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403);
}
$tanaman = Tanaman::findOrFail($id);
@@ -201,7 +201,7 @@ class TanamanController extends Controller
*/
public function destroy(string $id): JsonResponse
{
if (!Gate::any(['petani', 'fasilitator','super_admin'])) {
if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403);
}
$tanaman = Tanaman::findOrFail($id);
@@ -18,7 +18,7 @@ class PengurusBumdesController extends Controller
{
public function index(Request $request)
{
if (! Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (! Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403);
}
@@ -36,7 +36,7 @@ class PengurusBumdesController extends Controller
});
}
if (auth()->user()->role->value === 'fasilitator') {
if (auth()->user()->role->value === 'enumerator') {
$query->where('created_by', auth()->id());
}
@@ -64,7 +64,7 @@ class PengurusBumdesController extends Controller
public function create()
{
if (! Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (! Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403);
}
@@ -77,7 +77,7 @@ class PengurusBumdesController extends Controller
public function store(Request $request)
{
if (! Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (! Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403);
}
@@ -100,7 +100,7 @@ class PengurusBumdesController extends Controller
public function edit(string $id)
{
if (! Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (! Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403);
}
@@ -116,7 +116,7 @@ class PengurusBumdesController extends Controller
public function update(Request $request, string $id)
{
if (! Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (! Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403);
}
@@ -133,7 +133,7 @@ class PengurusBumdesController extends Controller
public function destroy(string $id)
{
if (! Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (! Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403);
}
@@ -146,7 +146,7 @@ class PengurusBumdesController extends Controller
public function kelembagaanAktor(string $id)
{
if (! Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (! Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403);
}
@@ -166,7 +166,7 @@ class PengurusBumdesController extends Controller
public function storeKelembagaanAktor(Request $request, string $id)
{
if (! Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (! Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403);
}
@@ -191,7 +191,7 @@ class PengurusBumdesController extends Controller
{
$query = PengurusBumdes::query();
if (auth()->user()->role->value === 'fasilitator') {
if (auth()->user()->role->value === 'enumerator') {
$query->where('created_by', auth()->id());
}
@@ -17,7 +17,7 @@ class DemografiPetaniController extends Controller
{
public function index(Request $request)
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403);
}
@@ -28,7 +28,7 @@ class DemografiPetaniController extends Controller
public function karakteristik(Request $request)
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403);
}
@@ -94,7 +94,7 @@ class IndeksPetaniController extends Controller
public function index(Request $request)
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403);
}
@@ -15,7 +15,7 @@ class InternationalMarketController extends Controller
{
public function __invoke()
{
abort_unless(Gate::allows('super_admin') || Gate::allows('admin') || Gate::allows('fasilitator'), 403);
abort_unless(Gate::allows('super_admin') || Gate::allows('admin') || Gate::allows('koordinator') || Gate::allows('enumerator'), 403);
// File cache keeps external quotes independent of the application database.
$cache = Cache::store('file');
@@ -17,7 +17,7 @@ class ProfilPetaniController extends Controller
{
public function index()
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403);
}
$title = 'Dashboard Profil Petani';
@@ -37,7 +37,7 @@ class SummaryController extends Controller
public function produktivitas(Request $request)
{
if (!Gate::allows('super_admin') && !Gate::allows('admin') && !Gate::allows('fasilitator')) {
if (!Gate::allows('super_admin') && !Gate::allows('admin') && !Gate::allows('koordinator') && !Gate::allows('enumerator')) {
abort(403);
}
@@ -59,7 +59,7 @@ class SummaryController extends Controller
public function mapInformation(Request $request)
{
if (!Gate::allows('super_admin') && !Gate::allows('admin') && !Gate::allows('fasilitator')) {
if (!Gate::allows('super_admin') && !Gate::allows('admin') && !Gate::allows('koordinator') && !Gate::allows('enumerator')) {
abort(403);
}
@@ -101,7 +101,7 @@ class SummaryController extends Controller
public function pasar(Request $request)
{
if (!Gate::allows('super_admin') && !Gate::allows('admin') && !Gate::allows('fasilitator')) {
if (!Gate::allows('super_admin') && !Gate::allows('admin') && !Gate::allows('koordinator') && !Gate::allows('enumerator')) {
abort(403);
}
@@ -31,7 +31,7 @@ class SurveyPetaniController extends Controller
// Progres per enumerator
$enumerators = User::query()
->where('role', 'fasilitator')
->where('role', 'enumerator')
->withCount([
'petaniDibuat as done',
])
@@ -24,7 +24,7 @@ class PetaniCPMController extends Controller
{
public function index(Request $request): View
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403);
}
@@ -60,7 +60,7 @@ class PetaniCPMController extends Controller
public function create(Request $request): View
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403);
}
@@ -94,7 +94,7 @@ class PetaniCPMController extends Controller
*/
public function store(Request $request): RedirectResponse
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403);
}
@@ -147,7 +147,7 @@ class PetaniCPMController extends Controller
*/
public function show(string $id): View
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403);
}
@@ -161,7 +161,7 @@ class PetaniCPMController extends Controller
*/
public function edit(Request $request, string $id): View
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403);
}
@@ -198,7 +198,7 @@ class PetaniCPMController extends Controller
*/
public function update(Request $request, string $id)
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403);
}
@@ -251,7 +251,7 @@ class PetaniCPMController extends Controller
*/
public function destroy(string $id)
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403);
}
@@ -34,7 +34,7 @@ class ValueBasisDataController extends Controller
public function index(Request $request, string $tabelId): View
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403);
}
@@ -0,0 +1,70 @@
<?php
namespace App\Http\Controllers\Web\Konfigurasi;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Gate;
/**
* Mengatur komoditas yang ditangani seorang koordinator.
*/
class KoordinatorKomoditasController extends Controller
{
public function update(Request $request, User $user)
{
abort_unless(Gate::any(['super_admin', 'admin']), 403);
if (! $user->isKoordinator()) {
return response()->json([
'success' => false,
'message' => 'Komoditas hanya bisa diatur untuk user dengan role Koordinator.',
], 422);
}
$validated = $request->validate([
'komoditas_ids' => ['required', 'array', 'min:1'],
'komoditas_ids.*' => ['integer', 'exists:master_komoditas,id'],
], [
'komoditas_ids.required' => 'Pilih minimal satu komoditas.',
'komoditas_ids.min' => 'Pilih minimal satu komoditas.',
'komoditas_ids.*.exists' => 'Komoditas yang dipilih tidak valid.',
]);
self::simpanPenugasan($user, $validated['komoditas_ids']);
return response()->json([
'success' => true,
'message' => 'Komoditas koordinator berhasil diperbarui.',
'data' => $user->komoditasKoordinatorIds(),
]);
}
/**
* Menyamakan penugasan koordinator dengan daftar komoditas yang dipilih.
* Penugasan yang dilepas di-soft delete, penugasan baru dibuat.
*/
public static function simpanPenugasan(User $user, array $komoditasIds): void
{
$komoditasIds = array_values(array_unique(array_map('intval', $komoditasIds)));
DB::transaction(function () use ($user, $komoditasIds) {
$user->koordinatorKomoditas()
->whereNotIn('komoditas_id', $komoditasIds)
->get()
->each->delete();
$sudahAda = $user->komoditasKoordinatorIds();
foreach (array_diff($komoditasIds, $sudahAda) as $komoditasId) {
$user->koordinatorKomoditas()->create([
'komoditas_id' => $komoditasId,
'created_by' => auth()->id(),
'updated_by' => auth()->id(),
]);
}
});
}
}
@@ -8,16 +8,29 @@ use App\Models\User;
use App\Models\Profile;
use App\Enums\Role;
use App\Models\Wilayah\Provinsi;
use App\Models\Pertanian\Komoditas;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
class UserController extends Controller
{
// Manajemen pengguna hanya untuk super admin & admin (sama dengan menu "Daftar Pengguna").
// Berlaku untuk semua method di controller ini.
public function __construct()
{
$this->middleware(function (Request $request, \Closure $next) {
abort_unless($request->user() && Gate::forUser($request->user())->any(['super_admin', 'admin']), 403);
return $next($request);
});
}
public function index(Request $request)
{
$query = User::orderBy('created_at', 'desc');
$query = User::with('koordinatorKomoditas.komoditas:id,nama')->orderBy('created_at', 'desc');
$tab = $request->get('tab', 'active');
if ($tab === 'pending') {
@@ -45,8 +58,15 @@ class UserController extends Controller
$pendingUsers = User::whereNull('email_verified_at')->count();
$roles = Role::cases();
$komoditasOptions = $this->komoditasOptions();
return view('konfigurasi.user.index', compact('users', 'totalUsers', 'activeUsers', 'pendingUsers', 'roles', 'tab'));
return view('konfigurasi.user.index', compact('users', 'totalUsers', 'activeUsers', 'pendingUsers', 'roles', 'tab', 'komoditasOptions'));
}
// Komoditas utama yang bisa ditugaskan ke koordinator
private function komoditasOptions()
{
return Komoditas::select('id', 'nama')->where('is_utama', true)->orderBy('nama')->get();
}
public function verify(Request $request, $id)
@@ -92,8 +112,9 @@ class UserController extends Controller
{
$roles = Role::cases();
$provinsi = Provinsi::orderBy('nama', 'asc')->get();
$komoditasOptions = $this->komoditasOptions();
return view('konfigurasi.user.add', compact('roles', 'provinsi'));
return view('konfigurasi.user.add', compact('roles', 'provinsi', 'komoditasOptions'));
}
public function store(Request $request)
@@ -104,6 +125,8 @@ class UserController extends Controller
'email' => ['required', 'string', 'email', 'max:255', 'unique:users,email'],
'password' => ['required', 'string', 'min:8', 'confirmed'],
'role' => ['required', 'string'],
'komoditas_ids' => ['nullable', 'array', 'required_if:role,' . Role::KOORDINATOR->value],
'komoditas_ids.*' => ['integer', 'exists:master_komoditas,id'],
'telepon' => ['required', 'max:13'],
'desa_kelurahan_id' => ['nullable', 'exists:desa_kelurahan,id'],
'ktp' => ['nullable', 'min:16', 'max:16'],
@@ -116,6 +139,7 @@ class UserController extends Controller
'password.confirmed' => 'Konfirmasi password tidak cocok!',
'password.min' => 'Password minimal 8 karakter!',
'email.unique' => 'Email sudah terdaftar.',
'komoditas_ids.required_if' => 'Pilih minimal satu komoditas untuk koordinator.',
'required' => 'Kolom :attribute wajib diisi.'
]);
@@ -163,6 +187,10 @@ class UserController extends Controller
'created_by' => auth()->id(),
'updated_by' => auth()->id(),
]);
if ($user->isKoordinator()) {
KoordinatorKomoditasController::simpanPenugasan($user, $request->input('komoditas_ids', []));
}
});
if ($request->expectsJson()) {
@@ -27,7 +27,7 @@ class AsetKelompokTaniController extends Controller
private function authorizeAccess(): void
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403);
}
}
@@ -16,7 +16,7 @@ class BantuanKelompokTaniController extends Controller
{
private function authorizeAccess(): void
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403);
}
}
@@ -17,7 +17,7 @@ class PendidikanPelatihanController extends Controller
{
private function authorizeAccess(): void
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403);
}
}
@@ -14,7 +14,7 @@ class PengurusKelompokTaniController extends Controller
{
private function authorizeAccess(): void
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403);
}
}
@@ -32,13 +32,13 @@ class PetaniController extends Controller
*/
public function index(Request $request)
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403);
}
$provinsi = Provinsi::select('id', 'kode', 'nama')->orderBy('kode', 'asc')->get();
$userRole = auth()->user()->role->value;
$showFasilitatorColumn = in_array($userRole, ['super_admin', 'admin'], true);
$showFasilitatorColumn = in_array($userRole, ['super_admin', 'admin', 'koordinator'], true);
$defaultKabupaten = KabupatenKota::select('id', 'nama')
->whereRaw('lower(nama) like ?', ['%ngada%'])
->orderBy('nama')
@@ -102,10 +102,19 @@ class PetaniController extends Controller
$query->where('desa_kelurahan_id', $request->desa_kelurahan_id);
}
if ($userRole === 'fasilitator') {
if ($userRole === 'enumerator') {
$query->where('created_by', auth()->user()->id);
}
// Koordinator hanya melihat petani dengan komoditas yang ditugaskan
$komoditasKoordinatorIds = $userRole === 'koordinator'
? auth()->user()->komoditasKoordinatorIds()
: null;
if ($komoditasKoordinatorIds !== null) {
$query->whereIn('komoditas_id', $komoditasKoordinatorIds);
}
$totalPetani = (clone $query)->count();
$totalPetaniLakiLaki = (clone $query)->where('jenis_kelamin', 1)->count();
$totalPetaniPerempuan = (clone $query)->where('jenis_kelamin', 0)->count();
@@ -165,10 +174,15 @@ class PetaniController extends Controller
->orWhereRaw('lower(ktp) like ?', ["%{$search}%"]);
});
})
->when($userRole === 'fasilitator', function ($query) {
->when($userRole === 'enumerator', function ($query) {
$query->whereHas('petani', function ($petani) {
$petani->where('created_by', auth()->id());
});
})
->when($komoditasKoordinatorIds !== null, function ($query) use ($komoditasKoordinatorIds) {
$query->whereHas('petani', function ($petani) use ($komoditasKoordinatorIds) {
$petani->whereIn('komoditas_id', $komoditasKoordinatorIds);
});
});
$totalLahan = (clone $lahanSummaryQuery)->count();
@@ -216,7 +230,7 @@ class PetaniController extends Controller
public function create()
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403);
}
@@ -288,7 +302,7 @@ class PetaniController extends Controller
public function store(Request $request)
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403);
}
@@ -320,7 +334,7 @@ class PetaniController extends Controller
public function show(string $id)
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403);
}
@@ -332,7 +346,7 @@ class PetaniController extends Controller
public function edit(string $id, Request $request)
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403);
}
@@ -353,7 +367,7 @@ class PetaniController extends Controller
public function update(Request $request, string $id)
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403);
}
@@ -395,14 +409,14 @@ class PetaniController extends Controller
public function destroy(string $id)
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
return response()->json(['message' => 'Anda tidak memiliki akses.'], 403);
}
try {
$petani = Petani::findOrFail($id);
if (auth()->user()->role->value === 'fasilitator' && $petani->created_by !== auth()->id()) {
if (auth()->user()->role->value === 'enumerator' && $petani->created_by !== auth()->id()) {
return response()->json(['message' => 'Anda tidak memiliki akses.'], 403);
}
@@ -429,7 +443,7 @@ class PetaniController extends Controller
public function showImage(string $id, string $field)
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403);
}
@@ -32,7 +32,7 @@ class PetaniLahanController extends Controller
{
public function index($petaniId, Request $request)
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403);
}
@@ -52,7 +52,7 @@ class PetaniLahanController extends Controller
public function create($petaniId)
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403);
}
@@ -194,7 +194,7 @@ class PetaniLahanController extends Controller
public function store($petaniId, Request $request)
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403);
}
@@ -292,7 +292,7 @@ class PetaniLahanController extends Controller
public function show(string $petaniId, string $id)
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403);
}
@@ -310,7 +310,7 @@ class PetaniLahanController extends Controller
public function edit(string $petaniId, string $id)
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403);
}
@@ -335,7 +335,7 @@ class PetaniLahanController extends Controller
public function update(Request $request, string $petaniId, string $id)
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403);
}
@@ -462,7 +462,7 @@ class PetaniLahanController extends Controller
public function destroy(string $petaniId, string $id)
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403);
}
@@ -481,7 +481,7 @@ class PetaniLahanController extends Controller
public function summary(string $petaniId)
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403);
}
@@ -42,7 +42,7 @@ class PetaniLahanTanamanController extends Controller
*/
public function show(string $petaniId, string $lahanId, string $id)
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403);
}
@@ -63,7 +63,7 @@ class PetaniLahanTanamanController extends Controller
public function update(Request $request, string $petaniId, string $lahanId, string $id)
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403);
}
@@ -103,7 +103,7 @@ class PetaniLahanTanamanController extends Controller
*/
public function destroy(string $petaniId, string $lahanId, string $id)
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403);
}
@@ -42,7 +42,7 @@ class PraktikBudidayaController extends Controller
{
public function index($petaniId, Request $request)
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403);
}
@@ -139,7 +139,7 @@ class PraktikBudidayaController extends Controller
public function store($petaniId, Request $request)
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403);
}
@@ -247,7 +247,7 @@ class PraktikBudidayaController extends Controller
public function edit($petaniId, $praktikBudidayaId)
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403);
}
@@ -340,7 +340,7 @@ class PraktikBudidayaController extends Controller
public function update($petaniId, $praktikBudidayaId, Request $request)
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403);
}
@@ -16,7 +16,7 @@ class SkorPetaniController extends Controller
*/
public function index(string $petaniId)
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403);
}
@@ -19,7 +19,7 @@ class SurveyKelompokTaniController extends Controller
{
private function authorizeAccess(): void
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
if (!Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403);
}
}
+45
View File
@@ -0,0 +1,45 @@
<?php
namespace App\Http\Middleware;
use App\Enums\Role;
use App\Models\Pertanian\Petani;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* Dipasang di semua route modul data petani (petani/{petani}/...).
* - Hanya role pengelola data petani yang boleh masuk.
* - Koordinator hanya boleh mengakses petani dengan komoditas utama yang ditugaskan.
*/
class BatasiAksesPetani
{
private const ROLE_DIIZINKAN = [
Role::SUPER_ADMIN,
Role::ADMIN,
Role::KOORDINATOR,
Role::ENUMERATOR,
];
public function handle(Request $request, Closure $next): Response
{
$user = $request->user();
abort_unless($user && in_array($user->role, self::ROLE_DIIZINKAN, true), 403);
$petani = $request->route('petani');
if ($petani !== null && $user->isKoordinator()) {
$petani = $petani instanceof Petani ? $petani : Petani::find($petani);
abort_unless(
$petani && $user->bolehAksesPetani($petani),
403,
'Petani ini bukan bagian dari komoditas yang Anda koordinasikan.'
);
}
return $next($request);
}
}
+17
View File
@@ -0,0 +1,17 @@
<?php
namespace App\Models\Concerns;
// Mengisi kolom deleted_by otomatis saat data di-soft delete (AISTANDAR 5.3).
trait HasDeletedBy
{
protected static function bootHasDeletedBy(): void
{
static::deleting(function ($model) {
if (! $model->isForceDeleting()) {
$model->deleted_by = auth()->id();
$model->saveQuietly();
}
});
}
}
@@ -0,0 +1,34 @@
<?php
namespace App\Models\Konfigurasi;
use App\Models\Concerns\HasDeletedBy;
use App\Models\Pertanian\Komoditas;
use App\Models\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes;
class KoordinatorKomoditas extends Model
{
use SoftDeletes, HasDeletedBy;
protected $table = 'koordinator_komoditas';
protected $fillable = [
'user_id',
'komoditas_id',
'created_by',
'updated_by',
];
public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id');
}
public function komoditas(): BelongsTo
{
return $this->belongsTo(Komoditas::class, 'komoditas_id');
}
}
+39
View File
@@ -12,6 +12,7 @@ use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Laravel\Sanctum\HasApiTokens;
use App\Models\Konfigurasi\KoordinatorKomoditas;
use App\Models\Pertanian\Petani;
class User extends Authenticatable
@@ -52,4 +53,42 @@ class User extends Authenticatable
{
return $this->hasMany(Petani::class, 'created_by');
}
protected static function booted(): void
{
static::deleting(function (User $user) {
if ($user->isForceDeleting()) {
return;
}
$user->koordinatorKomoditas()->get()->each->delete();
});
}
public function koordinatorKomoditas(): HasMany
{
return $this->hasMany(KoordinatorKomoditas::class, 'user_id');
}
public function isKoordinator(): bool
{
return $this->role === Role::KOORDINATOR;
}
// Daftar id komoditas yang ditugaskan ke koordinator ini.
public function komoditasKoordinatorIds(): array
{
return $this->koordinatorKomoditas()->pluck('komoditas_id')->all();
}
// Koordinator hanya boleh mengakses petani dengan komoditas utama yang ditugaskan.
// Role lain tidak dibatasi di sini (aturannya tetap di masing-masing controller).
public function bolehAksesPetani(Petani $petani): bool
{
if (! $this->isKoordinator()) {
return true;
}
return in_array($petani->komoditas_id, $this->komoditasKoordinatorIds());
}
}
+8
View File
@@ -54,6 +54,14 @@ class AppServiceProvider extends ServiceProvider
return $user->role === Role::ADMIN;
});
Gate::define('koordinator', function (User $user) {
return $user->role === Role::KOORDINATOR;
});
Gate::define('enumerator', function (User $user) {
return $user->role === Role::ENUMERATOR;
});
Gate::define('fasilitator', function (User $user) {
return $user->role === Role::FASILITATOR;
});
+4
View File
@@ -15,6 +15,10 @@ return Application::configure(basePath: dirname(__DIR__))
)
->withMiddleware(function (Middleware $middleware): void {
$middleware->append(\App\Http\Middleware\ResolveProgram::class);
$middleware->alias([
'akses.petani' => \App\Http\Middleware\BatasiAksesPetani::class,
]);
})
->withExceptions(function (Exceptions $exceptions): void {
//
@@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Penugasan user koordinator ke komoditas yang dia tangani.
*/
public function up(): void
{
Schema::create('koordinator_komoditas', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained('users');
$table->foreignId('komoditas_id')->constrained('master_komoditas');
$table->foreignId('created_by')->nullable()->constrained('users');
$table->foreignId('updated_by')->nullable()->constrained('users');
$table->timestamps();
$table->softDeletes();
$table->foreignId('deleted_by')->nullable()->constrained('users');
// Bukan unique index (sesuai AISTANDAR 5.2), cukup index untuk pencarian
$table->index(['user_id', 'komoditas_id']);
});
}
public function down(): void
{
Schema::dropIfExists('koordinator_komoditas');
}
};
+20 -1
View File
@@ -47,7 +47,7 @@
<option value="">Pilih Role</option>
@foreach($roles as $roleEnum)
<option value="{{ $roleEnum->value }}" {{ old('role', \App\Enums\Role::PETANI->value) === $roleEnum->value ? 'selected' : '' }}>
{{ ucfirst(str_replace('_', ' ', $roleEnum->value)) }}
{{ $roleEnum->label() }}
</option>
@endforeach
</select>
@@ -55,6 +55,19 @@
</div>
</div>
{{-- Hanya untuk role Koordinator --}}
<div class="row {{ old('role') === \App\Enums\Role::KOORDINATOR->value ? '' : 'd-none' }}" id="koordinator-komoditas-wrapper">
<div class="col-md-12 mb-4 form-control-validation">
<label class="form-label" for="add-koordinator-komoditas">Komoditas yang Dikoordinasikan<span class="text-danger">*</span></label>
<select id="add-koordinator-komoditas" name="komoditas_ids[]" class="select2 form-select @error('komoditas_ids') is-invalid @enderror" multiple data-placeholder="Pilih Komoditas" style="width: 100%">
@foreach($komoditasOptions as $komoditas)
<option value="{{ $komoditas->id }}" @selected(in_array($komoditas->id, old('komoditas_ids', [])))>{{ $komoditas->nama }}</option>
@endforeach
</select>
@error('komoditas_ids')<div class="invalid-feedback d-block">{{ $message }}</div>@enderror
</div>
</div>
<div class="row">
<div class="col-md-6 mb-4 form-control-validation">
<label class="form-label" for="add-profile-telepon">No. Telepon<span class="text-danger">*</span></label>
@@ -183,6 +196,12 @@
$(document).ready(function () {
$('.select2').select2();
// Pilihan komoditas hanya tampil untuk role Koordinator
$('#add-profile-role').on('change', function () {
const isKoordinator = $(this).val() === @json(\App\Enums\Role::KOORDINATOR->value);
$('#koordinator-komoditas-wrapper').toggleClass('d-none', !isKoordinator);
});
$(document).on('click', '.password-toggle', function () {
const input = this.closest('.input-group').querySelector('input');
const icon = this.querySelector('i');
@@ -111,7 +111,7 @@
<option value="">-- All Roles --</option>
@foreach($roles as $roleEnum)
<option value="{{ $roleEnum->value }}" {{ request('role') == $roleEnum->value ? 'selected' : '' }}>
{{ ucfirst(str_replace('_', ' ', $roleEnum->value)) }}
{{ $roleEnum->label() }}
</option>
@endforeach
</select>
@@ -164,14 +164,24 @@
$roleColors = [
'super_admin' => 'bg-label-danger',
'admin' => 'bg-label-warning',
'fasilitator' => 'bg-label-info',
'koordinator' => 'bg-label-primary',
'enumerator' => 'bg-label-info',
'fasilitator' => 'bg-label-dark',
'petani' => 'bg-label-success',
];
$color = $roleColors[$roleKey] ?? 'bg-label-secondary';
$roleLabel = ($user->role instanceof \App\Enums\Role)
? $user->role->label()
: ucfirst(str_replace('_', ' ', (string) $roleKey));
@endphp
<span class="badge {{ $color }}">
{{ ucfirst(str_replace('_', ' ', $roleKey)) }}
{{ $roleLabel }}
</span>
@if($roleKey === 'koordinator')
<small class="d-block text-muted mt-1">
{{ $user->koordinatorKomoditas->pluck('komoditas.nama')->filter()->join(', ') ?: 'Belum ada komoditas' }}
</small>
@endif
</td>
<td>
@if($user->email_verified_at)
@@ -192,6 +202,16 @@
<i class="ti tabler-trash ti-md"></i>
</a>
@if($roleKey === 'koordinator')
<a href="javascript:;" class="btn btn-icon btn-text-secondary rounded-pill btn-komoditas-koordinator"
title="Atur Komoditas"
data-url="{{ route('users.komoditas.update', $user->id) }}"
data-name="{{ $user->name }}"
data-selected='@json($user->koordinatorKomoditas->pluck('komoditas_id')->values())'>
<i class="ti tabler-plant ti-md"></i>
</a>
@endif
@if(!$user->email_verified_at)
<button type="button"
class="btn btn-primary px-9 d-flex align-items-center btn-verify shadow-sm waves-effect waves-light"
@@ -241,7 +261,9 @@
<option value="" selected disabled>-- Pilih Role Akses --</option>
<option value="super_admin">🟣 Super Admin</option>
<option value="admin">🟣 Admin</option>
<option value="fasilitator">🟣 Fasilitator</option>
<option value="koordinator">🟣 Koordinator</option>
<option value="enumerator">🟣 Enumerator</option>
<option value="fasilitator">🟣 Fasilitator (Pemateri Workshop)</option>
<option value="offtaker">🟣 Offtaker</option>
</select>
</div>
@@ -395,4 +417,67 @@
});
});
</script>
<script>
// Atur komoditas yang ditangani koordinator
$(function () {
const komoditasOptions = @json($komoditasOptions);
$(document).on('click', '.btn-komoditas-koordinator', function () {
const url = $(this).data('url');
const nama = $(this).data('name');
const selected = ($(this).data('selected') || []).map(Number);
const checkboxes = komoditasOptions.map(function (k) {
const checked = selected.includes(k.id) ? 'checked' : '';
return `<div class="form-check text-start mb-2">
<input class="form-check-input komoditas-koordinator-check" type="checkbox" value="${k.id}" id="kk-${k.id}" ${checked}>
<label class="form-check-label" for="kk-${k.id}">${$('<div>').text(k.nama).html()}</label>
</div>`;
}).join('');
Swal.fire({
title: 'Komoditas Koordinator',
html: `<p class="text-muted mb-4">Pilih komoditas yang dikoordinasikan oleh <strong>${$('<div>').text(nama).html()}</strong>.</p>
<div class="px-4">${checkboxes || '<p class="text-muted">Belum ada komoditas utama.</p>'}</div>`,
showCancelButton: true,
confirmButtonText: '<i class="ti tabler-check me-1"></i> Simpan',
cancelButtonText: 'Batal',
customClass: {
popup: 'rounded-4',
confirmButton: 'btn btn-primary me-3',
cancelButton: 'btn btn-label-secondary'
},
buttonsStyling: false,
preConfirm: function () {
const ids = Array.from(Swal.getPopup().querySelectorAll('.komoditas-koordinator-check:checked')).map(el => el.value);
if (ids.length === 0) {
Swal.showValidationMessage('Pilih minimal satu komoditas.');
return false;
}
return ids;
}
}).then(function (result) {
if (!result.isConfirmed) {
return;
}
$.ajax({
url: url,
type: 'PUT',
headers: { 'X-CSRF-TOKEN': csrfToken, 'Accept': 'application/json' },
data: { komoditas_ids: result.value },
success: function (res) {
Swal.fire({ icon: 'success', title: 'Berhasil!', text: res.message, showConfirmButton: false, timer: 1500 })
.then(() => location.reload());
},
error: function (xhr) {
const res = xhr.responseJSON || {};
const errors = res.errors ? Object.values(res.errors).flat().join('<br>') : null;
Swal.fire({ icon: 'error', title: 'Gagal!', html: errors || res.message || 'Gagal menyimpan komoditas.' });
}
});
});
});
});
</script>
@endpush
+3 -2
View File
@@ -26,7 +26,8 @@
$appName = env('APP_NAME');
$isAdmin = in_array($userRole, ['super_admin', 'admin'], true);
$isSuperAdmin = $userRole === 'super_admin';
$canViewDashboard = $isAdmin || $userRole === 'fasilitator';
$isKoordinator = $userRole === 'koordinator';
$canViewDashboard = $isAdmin || $isKoordinator || $userRole === 'enumerator';
@endphp
@if($canViewDashboard)
@@ -36,7 +37,7 @@
<div data-i18n="Dashboard">Dashboard</div>
</a>
@if($isAdmin && $appName === 'AgroMonev')
@if(($isAdmin || $isKoordinator) && $appName === 'AgroMonev')
<ul class="menu-sub">
<li class="menu-item {{ request()->routeIs(['dashboard.karakteristik-petani', 'dashboard.demografi-petani', 'dashboard.indeks-petani']) ? 'active' : '' }}">
<a href="{{ route('dashboard.karakteristik-petani') }}" class="menu-link">
@@ -315,7 +315,7 @@
<th>Komoditas Utama</th>
<th>Luas Lahan</th>
@if($showFasilitatorColumn)
<th>Fasilitator</th>
<th>Enumerator</th>
@endif
<th>Segmentasi</th>
<th>Aksi</th>
@@ -105,7 +105,7 @@
<p class="text-body-secondary small mb-0">Beberapa data belum tersedia. Lengkapi agar informasi profil dan penilaian lebih utuh.</p>
</div>
</div>
@canany(['super_admin', 'admin', 'fasilitator'])
@canany(['super_admin', 'admin', 'enumerator'])
<a href="{{ route('petani.edit', $petani->id) }}" class="btn btn-sm btn-primary flex-shrink-0 profile-completeness-action">
<i class="icon-base ti tabler-edit icon-sm me-2" aria-hidden="true"></i>Lengkapi data
</a>
+11 -4
View File
@@ -3,7 +3,6 @@
use Illuminate\Support\Facades\Route;
//Auth
Route::put('/users/{id}/verify', [App\Http\Controllers\Web\Konfigurasi\UserController::class, 'verify'])->name('users.verify');
Route::get('/login', [App\Http\Controllers\Web\Konfigurasi\AuthController::class, 'login'])->name('login');
Route::get('/register', [App\Http\Controllers\Web\Konfigurasi\AuthController::class, 'register'])->name('register');
Route::post('/create-account', [App\Http\Controllers\Web\Konfigurasi\AuthController::class, 'createAccount'])->name('auth.createAccount');
@@ -17,10 +16,15 @@ Route::get('/', function() {
return redirect()->route('login');
}
if (auth()->user()->role->value === 'fasilitator') {
if (auth()->user()->role->value === 'enumerator') {
return redirect()->route('dashboard.survey-petani');
}
// Fasilitator (pemateri workshop) belum punya menu khusus
if (auth()->user()->role->value === 'fasilitator') {
return redirect()->route('userProfile.index');
}
return redirect()->route('dashboard.karakteristik-petani');
})->name('home');
@@ -82,7 +86,8 @@ Route::middleware('auth')
// User
Route::resource('users', App\Http\Controllers\Web\Konfigurasi\UserController::class);
Route::patch('/users/{user}/verify', [App\Http\Controllers\Web\Konfigurasi\UserController::class, 'verify'])->name('users.verify');
Route::put('/users/{user}/verify', [App\Http\Controllers\Web\Konfigurasi\UserController::class, 'verify'])->name('users.verify');
Route::singleton('users.komoditas', App\Http\Controllers\Web\Konfigurasi\KoordinatorKomoditasController::class)->only(['update']);
// Provinsi
Route::get('/provinsi/list', [App\Http\Controllers\Web\Master\ProvinsiController::class, 'list'])->name('provinsi.list');
@@ -220,9 +225,10 @@ Route::middleware('auth')
Route::resource('realisasi', App\Http\Controllers\Web\Program\RealisasiController::class)->only('index', 'store', 'show', 'update', 'destroy');
Route::resource('verifikasi', App\Http\Controllers\Web\Program\VerifikasiController::class)->only('store', 'destroy');
// Petani (dibatasi per role & komoditas koordinator, lihat BatasiAksesPetani)
Route::middleware('akses.petani')->group(function () {
Route::get('petani/show-image/{id}/{field}', [App\Http\Controllers\Web\Produktivitas\PetaniController::class, 'showImage'])->name('petani.show-image');
// Petani
Route::resource('petani', App\Http\Controllers\Web\Produktivitas\PetaniController::class)->whereNumber('petani');
Route::get('petani/{petani}/akses-pasar', [App\Http\Controllers\Web\Produktivitas\AksesPasarController::class, 'editByPetani'])->name('petani.akses-pasar.edit');
Route::get('petani/{petani}/lahan-tanaman/summary', [App\Http\Controllers\Web\Produktivitas\PetaniLahanController::class, 'summary'])->name('petani.lahan-tanaman.summary');
@@ -243,6 +249,7 @@ Route::middleware('auth')
Route::resource('petani.akses-pasar', App\Http\Controllers\Web\Produktivitas\AksesPasarController::class)->whereNumber('petani');
Route::resource('petani.skor', App\Http\Controllers\Web\Produktivitas\SkorPetaniController::class)->only(['index']);
});
Route::resource('survey/kelompok-tani', App\Http\Controllers\Web\Produktivitas\SurveyKelompokTaniController::class)->except(['show'])->names('kelompok-tani')->parameters(['kelompok-tani' => 'kelompokTani']);
Route::prefix('survey/kelompok-tani/{kelompokTani}')->name('kelompok-tani.')->group(function () {