feat: tambah role editor, workflow post, leaderboard, rekap kehadiran, kategori kas dengan type, seeder lengkap
This commit is contained in:
@@ -10,7 +10,7 @@ return new class extends Migration
|
||||
{
|
||||
Schema::create('cash_categories', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name'); // pemasukan / pengeluaran
|
||||
$table->string('name'); // nama bebas, tipe dikontrol via kolom type
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('cash_categories', function (Blueprint $table) {
|
||||
$table->enum('type', ['pemasukan', 'pengeluaran'])->after('name')->default('pemasukan');
|
||||
});
|
||||
|
||||
// Migrate existing data
|
||||
DB::table('cash_categories')->where('name', 'pengeluaran')->update(['type' => 'pengeluaran']);
|
||||
DB::table('cash_categories')->where('name', '!=', 'pengeluaran')->update(['type' => 'pemasukan']);
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('cash_categories', function (Blueprint $table) {
|
||||
$table->dropColumn('type');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('posts', function (Blueprint $table) {
|
||||
// Ubah enum status tambah 'approved'
|
||||
$table->enum('status', ['draft', 'pending', 'approved', 'published', 'rejected'])
|
||||
->default('draft')->change();
|
||||
|
||||
$table->foreignId('approved_by')->nullable()->constrained('users')->after('reviewed_by');
|
||||
$table->timestamp('approved_at')->nullable()->after('approved_by');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('posts', function (Blueprint $table) {
|
||||
$table->dropForeign(['approved_by']);
|
||||
$table->dropColumn(['approved_by', 'approved_at']);
|
||||
$table->enum('status', ['draft', 'pending', 'published', 'rejected'])
|
||||
->default('draft')->change();
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -47,11 +47,16 @@ class ActivitySeeder extends Seeder
|
||||
foreach ($activities as $data) {
|
||||
$activity = Activity::create(array_merge($data, ['created_by' => $pengurus?->id]));
|
||||
|
||||
// Attach peserta dengan status kehadiran
|
||||
$syncData = $anggota->mapWithKeys(fn ($user) => [
|
||||
$user->id => ['status' => 'hadir', 'notes' => null]
|
||||
])->toArray();
|
||||
$activity->participants()->sync($syncData);
|
||||
// Hanya kegiatan yang sudah executed yang punya data kehadiran
|
||||
if (! empty($data['executed_at'])) {
|
||||
$syncData = $anggota->mapWithKeys(fn ($user, $i) => [
|
||||
$user->id => [
|
||||
'status' => $i % 4 === 0 ? 'izin' : 'hadir',
|
||||
'notes' => null,
|
||||
]
|
||||
])->toArray();
|
||||
$activity->participants()->sync($syncData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\Approval;
|
||||
use App\Models\CashCategory;
|
||||
use App\Models\CashRecord;
|
||||
use App\Models\User;
|
||||
@@ -11,8 +12,8 @@ class CashSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
$pemasukan = CashCategory::firstOrCreate(['name' => 'pemasukan']);
|
||||
$pengeluaran = CashCategory::firstOrCreate(['name' => 'pengeluaran']);
|
||||
$pemasukan = CashCategory::firstOrCreate(['name' => 'Iuran & Donasi'], ['type' => 'pemasukan']);
|
||||
$pengeluaran = CashCategory::firstOrCreate(['name' => 'Operasional'], ['type' => 'pengeluaran']);
|
||||
|
||||
$bendahara = User::role('bendahara')->first();
|
||||
$ketua = User::role('ketua')->first();
|
||||
@@ -26,11 +27,30 @@ class CashSeeder extends Seeder
|
||||
];
|
||||
|
||||
foreach ($records as $data) {
|
||||
CashRecord::create(array_merge($data, [
|
||||
'created_by' => $bendahara?->id,
|
||||
'verified_by' => $ketua?->id,
|
||||
'verified_at' => now()->subDays(1),
|
||||
]));
|
||||
$amount = $data['amount'];
|
||||
|
||||
// 500rb–2jt: butuh approval ketua, belum verified
|
||||
if ($amount >= 500_000 && $amount <= 2_000_000) {
|
||||
$record = CashRecord::create(array_merge($data, [
|
||||
'created_by' => $bendahara?->id,
|
||||
'verified_by' => null,
|
||||
'verified_at' => null,
|
||||
]));
|
||||
|
||||
Approval::create([
|
||||
'model_type' => CashRecord::class,
|
||||
'model_id' => $record->id,
|
||||
'required_approvals' => 1,
|
||||
'status' => 'pending',
|
||||
]);
|
||||
} else {
|
||||
// < 500rb: langsung verified
|
||||
CashRecord::create(array_merge($data, [
|
||||
'created_by' => $bendahara?->id,
|
||||
'verified_by' => $ketua?->id,
|
||||
'verified_at' => now()->subDays(1),
|
||||
]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\ContactMessage;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class ContactMessageSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
$messages = [
|
||||
['name' => 'Budi Santoso', 'email' => 'budi@example.com', 'phone' => '08123456789', 'subject' => 'Pendaftaran anggota baru', 'message' => 'Saya ingin bergabung dengan organisasi Persegi. Bagaimana cara mendaftarnya?'],
|
||||
['name' => 'Siti Rahayu', 'email' => null, 'phone' => '08987654321', 'subject' => 'Informasi kegiatan kerja bakti', 'message' => 'Apakah masyarakat umum boleh ikut kegiatan kerja bakti yang akan datang?'],
|
||||
['name' => 'Ahmad Fauzi', 'email' => 'ahmad@example.com', 'phone' => null, 'subject' => 'Donasi untuk kegiatan', 'message' => 'Saya ingin berdonasi untuk mendukung kegiatan. Kemana saya bisa menghubungi bendahara?'],
|
||||
['name' => 'Dewi Lestari', 'email' => 'dewi@example.com', 'phone' => '08111222333', 'subject' => 'Jadwal rapat bulanan', 'message' => 'Kapan jadwal rapat bulanan berikutnya? Saya ingin hadir sebagai tamu.'],
|
||||
['name' => 'Hendra Wijaya', 'email' => null, 'phone' => '08555666777', 'subject' => 'Laporan kerusakan fasilitas desa', 'message' => 'Ada lampu jalan yang mati di RT 03. Apakah Persegi bisa membantu melaporkan ke desa?'],
|
||||
['name' => 'Rina Kusuma', 'email' => 'rina@example.com', 'phone' => '08222333444', 'subject' => 'Kerjasama kegiatan sosial', 'message' => 'Kami dari karang taruna RT 05 ingin mengajak kerjasama untuk kegiatan sosial bulan depan.'],
|
||||
['name' => 'Joko Pramono', 'email' => null, 'phone' => '08444555666', 'subject' => 'Pertanyaan iuran anggota', 'message' => 'Berapa besaran iuran bulanan anggota Persegi? Saya tertarik untuk bergabung.'],
|
||||
['name' => 'Nurul Hidayah', 'email' => 'nurul@example.com', 'phone' => '08777888999', 'subject' => 'Saran program kerja', 'message' => 'Saya punya usulan program pelatihan digital untuk pemuda desa. Bagaimana cara menyampaikannya?'],
|
||||
];
|
||||
|
||||
foreach ($messages as $data) {
|
||||
ContactMessage::create($data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,8 @@ class DatabaseSeeder extends Seeder
|
||||
VoteSeeder::class,
|
||||
PostSeeder::class,
|
||||
AuditSeeder::class,
|
||||
MemberDueSeeder::class,
|
||||
ContactMessageSeeder::class,
|
||||
MemberPointSeeder::class,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\MemberDue;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class MemberDueSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
$bendahara = User::role('bendahara')->first();
|
||||
|
||||
$members = User::whereDoesntHave('roles', fn ($q) => $q->whereIn('name', ['super_admin', 'bendahara']))
|
||||
->get();
|
||||
|
||||
$periods = [
|
||||
now()->subMonths(3)->format('Y-m'),
|
||||
now()->subMonths(2)->format('Y-m'),
|
||||
now()->subMonth()->format('Y-m'),
|
||||
now()->format('Y-m'),
|
||||
];
|
||||
|
||||
foreach ($members as $i => $user) {
|
||||
foreach ($periods as $j => $period) {
|
||||
MemberDue::firstOrCreate(
|
||||
['user_id' => $user->id, 'period' => $period],
|
||||
[
|
||||
'amount' => 10000,
|
||||
'status' => ($i + $j) % 3 === 0 ? 'belum' : 'lunas',
|
||||
'created_by' => $bendahara?->id,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,17 +16,17 @@ class MemberPointSeeder extends Seeder
|
||||
Activity::whereNotNull('executed_at')->each(function ($activity) {
|
||||
$activity->participants()->wherePivot('status', 'hadir')->each(function ($user) use ($activity) {
|
||||
MemberPoint::firstOrCreate(
|
||||
['user_id' => $user->id, 'source_type' => 'activity', 'source_id' => $activity->id],
|
||||
['user_id' => $user->id, 'source_type' => Activity::class, 'source_id' => $activity->id],
|
||||
['points' => 10, 'reason' => "Hadir di kegiatan: {$activity->title}"]
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// Poin dari artikel yang sudah published
|
||||
Post::where('status', 'published')->each(function ($post) {
|
||||
// Poin dari artikel yang sudah approved
|
||||
Post::where('status', 'approved')->orWhere('status', 'published')->each(function ($post) {
|
||||
MemberPoint::firstOrCreate(
|
||||
['user_id' => $post->author_id, 'source_type' => 'post', 'source_id' => $post->id],
|
||||
['points' => 5, 'reason' => "Artikel dipublikasi: {$post->title}"]
|
||||
['user_id' => $post->author_id, 'source_type' => Post::class, 'source_id' => $post->id],
|
||||
['points' => 5, 'reason' => "Artikel disetujui: {$post->title}"]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ class PermissionSeeder extends Seeder
|
||||
app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();
|
||||
|
||||
// Buat roles jika belum ada
|
||||
foreach (['super_admin', 'ketua', 'bendahara', 'pengurus', 'anggota', 'auditor'] as $role) {
|
||||
foreach (['super_admin', 'ketua', 'bendahara', 'pengurus', 'anggota', 'auditor', 'editor'] as $role) {
|
||||
Role::firstOrCreate(['name' => $role, 'guard_name' => 'web']);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ class PermissionSeeder extends Seeder
|
||||
$pengurus = Role::findByName('pengurus');
|
||||
$anggota = Role::findByName('anggota');
|
||||
$auditor = Role::findByName('auditor');
|
||||
$editor = Role::findByName('editor');
|
||||
|
||||
$ketua->syncPermissions(Permission::where('name', 'not like', '%Role%')
|
||||
->where('name', 'not like', '%Permission%')
|
||||
@@ -63,5 +64,11 @@ class PermissionSeeder extends Seeder
|
||||
->orWhere('name', 'like', 'View:%')
|
||||
->orWhere('name', 'like', '%Audit%')
|
||||
->get());
|
||||
|
||||
$editor->syncPermissions(Permission::whereIn('name', [
|
||||
'ViewAny:Post', 'View:Post', 'Create:Post', 'Update:Post', 'Delete:Post',
|
||||
'DeleteAny:Post', 'ViewAny:Activity', 'View:Activity',
|
||||
'Publish:Post',
|
||||
])->get());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,38 +5,87 @@ namespace Database\Seeders;
|
||||
use App\Models\Post;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class PostSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
$author = User::role('ketua')->first() ?? User::first();
|
||||
$editor = User::role('editor')->first();
|
||||
$anggota = User::role('anggota')->first();
|
||||
$ketua = User::role('ketua')->first();
|
||||
|
||||
$posts = [
|
||||
// Published — sudah melalui full workflow
|
||||
[
|
||||
'title' => 'Selamat Datang di Website Persegi',
|
||||
'category' => 'pengumuman',
|
||||
'content' => '<p>Kami dengan bangga mempersembahkan website resmi organisasi Persegi. Melalui website ini, masyarakat dapat mengikuti perkembangan kegiatan dan informasi terbaru dari organisasi kami.</p>',
|
||||
'author_id' => $ketua?->id,
|
||||
'status' => 'published',
|
||||
'approved_by' => $editor?->id,
|
||||
'approved_at' => now()->subDays(11),
|
||||
'reviewed_by' => $editor?->id,
|
||||
'published_at' => now()->subDays(10),
|
||||
],
|
||||
[
|
||||
'title' => 'Rekrutmen Anggota Baru 2026',
|
||||
'category' => 'pengumuman',
|
||||
'content' => '<p>Persegi membuka pendaftaran anggota baru untuk periode 2026. Bagi pemuda Desa Karangdadap yang ingin bergabung, silakan hubungi pengurus melalui kontak yang tersedia.</p><p>Pendaftaran dibuka hingga akhir bulan April 2026.</p>',
|
||||
'author_id' => $ketua?->id,
|
||||
'status' => 'published',
|
||||
'approved_by' => $editor?->id,
|
||||
'approved_at' => now()->subDays(6),
|
||||
'reviewed_by' => $editor?->id,
|
||||
'published_at' => now()->subDays(5),
|
||||
],
|
||||
[
|
||||
'title' => 'Laporan Kegiatan Kerja Bakti Desa',
|
||||
'category' => 'berita',
|
||||
'content' => '<p>Kegiatan kerja bakti yang dilaksanakan pada bulan lalu berjalan dengan lancar. Sebanyak 30 anggota turut berpartisipasi dalam membersihkan lingkungan desa.</p><p>Terima kasih kepada seluruh anggota yang telah berkontribusi.</p>',
|
||||
'author_id' => $anggota?->id,
|
||||
'status' => 'published',
|
||||
'approved_by' => $editor?->id,
|
||||
'approved_at' => now()->subDays(3),
|
||||
'reviewed_by' => $editor?->id,
|
||||
'published_at' => now()->subDays(2),
|
||||
],
|
||||
// Approved — sudah disetujui editor, belum diterbitkan
|
||||
[
|
||||
'title' => 'Jadwal Rapat Bulanan April 2026',
|
||||
'category' => 'pengumuman',
|
||||
'content' => '<p>Rapat bulanan akan dilaksanakan pada akhir April 2026. Seluruh anggota diharapkan hadir.</p>',
|
||||
'author_id' => $anggota?->id,
|
||||
'status' => 'approved',
|
||||
'approved_by' => $editor?->id,
|
||||
'approved_at' => now()->subDay(),
|
||||
'reviewed_by' => $editor?->id,
|
||||
'published_at' => null,
|
||||
],
|
||||
// Pending — menunggu review editor
|
||||
[
|
||||
'title' => 'Kegiatan Sosial Ramadan 2026',
|
||||
'category' => 'berita',
|
||||
'content' => '<p>Persegi berencana mengadakan kegiatan sosial berbagi sembako selama bulan Ramadan.</p>',
|
||||
'author_id' => $anggota?->id,
|
||||
'status' => 'pending',
|
||||
'published_at' => null,
|
||||
],
|
||||
// Draft — belum diajukan
|
||||
[
|
||||
'title' => 'Profil Divisi Olahraga',
|
||||
'category' => 'umum',
|
||||
'content' => '<p>Divisi olahraga Persegi aktif mengadakan kegiatan rutin setiap minggu.</p>',
|
||||
'author_id' => $anggota?->id,
|
||||
'status' => 'draft',
|
||||
'published_at' => null,
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($posts as $data) {
|
||||
Post::firstOrCreate(
|
||||
['slug' => \Illuminate\Support\Str::slug($data['title'])],
|
||||
array_merge($data, ['author_id' => $author->id, 'status' => 'published'])
|
||||
['slug' => Str::slug($data['title'])],
|
||||
$data
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,15 @@ class UserSeeder extends Seeder
|
||||
->each(fn ($user) => $user->assignRole($role));
|
||||
}
|
||||
|
||||
// 1 editor
|
||||
User::factory()->createOne([
|
||||
'name' => 'Editor Konten',
|
||||
'email' => 'editor@persegi.test',
|
||||
'password' => bcrypt('password'),
|
||||
'status' => 'aktif',
|
||||
'division_id' => fake()->randomElement($divisions),
|
||||
])->assignRole('editor');
|
||||
|
||||
// 2 user tanpa role
|
||||
User::factory(2)->create(['division_id' => fake()->randomElement($divisions)]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user