Problem Statement: Freelancer sering kesulitan melacak progress proyek yang berjalan paralel, invoice yang belum dibayar, dan tenggat waktu (deadline) karena data tersebar di berbagai platform (Excel, WhatsApp, Email).
Dibutuhkan sebuah sistem terpusat yang bisa mengelola Database Klien (clients), Progress Proyek (projects), dan Riwayat Pembayaran (payments) dalam satu antarmuka dashboard yang efisien.
Saya membangun Freelance OS menggunakan CodeIgniter 4 dengan arsitektur MVC. Fokus utama adalah efisiensi query dan struktur data yang terintegrasi penuh.
ON DELETE CASCADE) agar data klien dan proyek selalu sinkron.Pending -> On Progress -> Selesai) menggunakan tipe data ENUM di database.
Berdasarkan struktur db_freelance.sql, arsitektur database dirancang untuk skalabilitas CRM (Customer Relationship Management).
clients: Menyimpan data master klien (Nama, Instansi, WA, Email).projects: Terhubung langsung ke Client ID. Memiliki atribut esensial seperti nilai_proyek dan tanggal_deadline.payments: Terhubung ke Project ID. Mencatat riwayat cicilan pembayaran (jumlah_bayar) secara bertahap.Berikut adalah logika Backend (CI4 Model) yang saya kembangkan untuk menghitung kesehatan finansial per proyek secara real-time (Total Nilai Proyek vs Total Dana yang sudah Terbayar).
public function getProjectFinancials($projectId) {
// 1. Ambil Data Proyek (CI4 Model)
$project = $this->find($projectId);
// 2. Hitung Total Pembayaran Masuk (Menggunakan Query Builder)
$db = \Config\Database::connect();
$totalPaid = $db->table('payments')
->where('project_id', $projectId)
->selectSum('jumlah_bayar')
->get()->getRow()->jumlah_bayar;
// 3. Kalkulasi Sisa Tagihan & Progress Pembayaran (Percentage)
$remaining = $project['nilai_proyek'] - $totalPaid;
$percentage = ($totalPaid > 0) ? ($totalPaid / $project['nilai_proyek']) * 100 : 0;
return [
'project_name' => $project['nama_project'],
'contract_value' => $project['nilai_proyek'],
'paid_amount' => $totalPaid,
'remaining_balance' => $remaining,
'payment_status' => ($remaining <= 0) ? 'PAID OFF' : 'UNPAID'
];
}