Modul 06: Admin Manajemen Anggota & Sirkulasi Transaksi

Modul 06: Admin Manajemen Anggota & Sirkulasi Transaksi

Berikut adalah seluruh berkas kode program aktual yang digunakan dalam aplikasi SIPAS tanpa potongan atau placeholder. Kode dapat dipelajari dan diketikkan langsung pada Sublime Text 3.

1

Model Sirkulasi Transaksi (Transaksi_model.php)

Lokasi Berkas: application/models/Transaksi_model.php

application/models/Transaksi_model.php PHP
<?php
defined('BASEPATH') OR exit('No direct script access allowed');

/**
 * Transaksi_model
 * Model untuk menangani peminjaman, pengembalian, dan riwayat transaksi
 */
class Transaksi_model extends CI_Model {

    private $table = 'transaksi';

    public function __construct() {
        parent::__construct();
    }

    /**
     * Mengambil daftar transaksi dengan relasi users dan buku
     */
    public function get_all($limit = NULL, $offset = NULL, $status = NULL, $search = NULL) {
        $this->db->select('transaksi.*, users.nama as nama_siswa, users.nis, users.kelas, buku.kode_buku, buku.judul as judul_buku');
        $this->db->from($this->table);
        $this->db->join('users', 'users.id = transaksi.user_id', 'left');
        $this->db->join('buku', 'buku.id = transaksi.buku_id', 'left');

        if (!empty($status) && $status !== 'semua') {
            $this->db->where('transaksi.status', $status);
        }

        if (!empty($search)) {
            $this->db->group_start();
            $this->db->like('transaksi.kode_transaksi', $search);
            $this->db->or_like('users.nama', $search);
            $this->db->or_like('users.nis', $search);
            $this->db->or_like('buku.judul', $search);
            $this->db->group_end();
        }

        $this->db->order_by('transaksi.created_at', 'DESC');

        if ($limit !== NULL) {
            $this->db->limit($limit, $offset);
        }

        return $this->db->get()->result();
    }

    /**
     * Menghitung total data transaksi untuk pagination
     */
    public function count_all($status = NULL, $search = NULL) {
        $this->db->from($this->table);
        $this->db->join('users', 'users.id = transaksi.user_id', 'left');
        $this->db->join('buku', 'buku.id = transaksi.buku_id', 'left');

        if (!empty($status) && $status !== 'semua') {
            $this->db->where('transaksi.status', $status);
        }

        if (!empty($search)) {
            $this->db->group_start();
            $this->db->like('transaksi.kode_transaksi', $search);
            $this->db->or_like('users.nama', $search);
            $this->db->or_like('users.nis', $search);
            $this->db->or_like('buku.judul', $search);
            $this->db->group_end();
        }

        return $this->db->count_all_results();
    }

    /**
     * Mengambil detail transaksi lengkap berdasarkan ID
     */
    public function get_by_id($id) {
        $this->db->select('transaksi.*, users.nama as nama_siswa, users.nis, users.kelas, users.username, buku.kode_buku, buku.judul as judul_buku, buku.penulis, buku.penerbit, buku.rak');
        $this->db->from($this->table);
        $this->db->join('users', 'users.id = transaksi.user_id', 'left');
        $this->db->join('buku', 'buku.id = transaksi.buku_id', 'left');
        $this->db->where('transaksi.id', $id);
        return $this->db->get()->row();
    }

    /**
     * Mengambil transaksi berdasarkan user tertentu
     */
    public function get_by_user($user_id, $status = NULL, $search = NULL, $limit = NULL, $offset = NULL) {
        $this->db->select('transaksi.*, buku.kode_buku, buku.judul as judul_buku, buku.penulis, buku.penerbit, buku.rak, buku.kategori');
        $this->db->from($this->table);
        $this->db->join('buku', 'buku.id = transaksi.buku_id', 'left');
        $this->db->where('transaksi.user_id', $user_id);

        if (!empty($status) && $status !== 'semua') {
            $this->db->where('transaksi.status', $status);
        }

        if (!empty($search)) {
            $this->db->group_start();
            $this->db->like('buku.judul', $search);
            $this->db->or_like('transaksi.kode_transaksi', $search);
            $this->db->group_end();
        }

        $this->db->order_by('transaksi.created_at', 'DESC');

        if ($limit !== NULL) {
            $this->db->limit($limit, $offset);
        }

        return $this->db->get()->result();
    }

    /**
     * Hitung total transaksi milik user
     */
    public function count_by_user($user_id, $status = NULL, $search = NULL) {
        $this->db->from($this->table);
        $this->db->join('buku', 'buku.id = transaksi.buku_id', 'left');
        $this->db->where('transaksi.user_id', $user_id);

        if (!empty($status) && $status !== 'semua') {
            $this->db->where('transaksi.status', $status);
        }

        if (!empty($search)) {
            $this->db->group_start();
            $this->db->like('buku.judul', $search);
            $this->db->or_like('transaksi.kode_transaksi', $search);
            $this->db->group_end();
        }

        return $this->db->count_all_results();
    }

    /**
     * Mengambil transaksi aktif (dipinjam) oleh user
     */
    public function get_active_by_user($user_id) {
        return $this->get_by_user($user_id, 'dipinjam');
    }

    /**
     * Cek apakah user sedang meminjam buku yang sama dan belum dikembalikan
     */
    public function is_book_borrowed_by_user($user_id, $buku_id) {
        $this->db->where('user_id', $user_id);
        $this->db->where('buku_id', $buku_id);
        $this->db->where('status', 'dipinjam');
        return $this->db->count_all_results($this->table) > 0;
    }

    /**
     * Generate Kode Transaksi Unik: TRX + YYYYMMDD + 4 digit urut
     */
    public function generate_kode() {
        $date = date('Ymd');
        $prefix = 'TRX' . $date;
        $this->db->like('kode_transaksi', $prefix, 'after');
        $this->db->order_by('kode_transaksi', 'DESC');
        $last = $this->db->get($this->table, 1)->row();

        if ($last) {
            $last_num = (int) substr($last->kode_transaksi, -4);
            $next_num = str_pad($last_num + 1, 4, '0', STR_PAD_LEFT);
        } else {
            $next_num = '0001';
        }

        return $prefix . $next_num;
    }

    /**
     * Membuat Transaksi Peminjaman Baru dengan Database Transaction
     */
    public function create_peminjaman($user_id, $buku_id) {
        $this->db->trans_start();

        // 1. Ambil info buku & cek stok
        $buku = $this->db->get_where('buku', ['id' => $buku_id])->row();
        if (!$buku || $buku->stok <= 0) {
            $this->db->trans_rollback();
            return ['status' => false, 'message' => 'Buku sedang tidak tersedia atau stok habis.'];
        }

        // 2. Cek apakah user sedang meminjam buku yang sama
        if ($this->is_book_borrowed_by_user($user_id, $buku_id)) {
            $this->db->trans_rollback();
            return ['status' => false, 'message' => 'Anda masih meminjam buku ini. Harap kembalikan terlebih dahulu.'];
        }

        // 3. Generate kode transaksi & tanggal
        $kode_transaksi = $this->generate_kode();
        $tanggal_pinjam = date('Y-m-d');
        $batas_kembali = date('Y-m-d', strtotime('+7 days'));

        $data_transaksi = [
            'kode_transaksi' => $kode_transaksi,
            'user_id' => $user_id,
            'buku_id' => $buku_id,
            'tanggal_pinjam' => $tanggal_pinjam,
            'batas_kembali' => $batas_kembali,
            'tanggal_kembali' => NULL,
            'status' => 'dipinjam',
            'created_at' => date('Y-m-d H:i:s'),
            'updated_at' => date('Y-m-d H:i:s')
        ];

        $this->db->insert($this->table, $data_transaksi);

        // 4. Kurangi stok buku - 1
        $this->db->set('stok', 'stok - 1', FALSE);
        $this->db->where('id', $buku_id);
        $this->db->update('buku');

        $this->db->trans_complete();

        if ($this->db->trans_status() === FALSE) {
            return ['status' => false, 'message' => 'Gagal memproses transaksi peminjaman.'];
        }

        return ['status' => true, 'message' => 'Peminjaman berhasil dilakukan. Kode: ' . $kode_transaksi];
    }

    /**
     * Memproses Pengembalian Buku dengan Database Transaction
     */
    public function proses_pengembalian($transaksi_id, $allowed_user_id = NULL) {
        $this->db->trans_start();

        // 1. Ambil data transaksi
        $transaksi = $this->db->get_where($this->table, ['id' => $transaksi_id])->row();
        if (!$transaksi) {
            $this->db->trans_rollback();
            return ['status' => false, 'message' => 'Data transaksi tidak ditemukan.'];
        }

        // Proteksi kepemilikan transaksi jika dipanggil oleh role user
        if ($allowed_user_id !== NULL && $transaksi->user_id != $allowed_user_id) {
            $this->db->trans_rollback();
            return ['status' => false, 'message' => 'Anda tidak memiliki hak untuk mengembalikan transaksi ini.'];
        }

        // 2. Cek apakah sudah berstatus dikembalikan
        if ($transaksi->status === 'dikembalikan') {
            $this->db->trans_rollback();
            return ['status' => false, 'message' => 'Buku pada transaksi ini sudah pernah dikembalikan.'];
        }

        // 3. Update status transaksi
        $this->db->where('id', $transaksi_id);
        $this->db->update($this->table, [
            'status' => 'dikembalikan',
            'tanggal_kembali' => date('Y-m-d'),
            'updated_at' => date('Y-m-d H:i:s')
        ]);

        // 4. Kembalikan stok buku + 1
        $this->db->set('stok', 'stok + 1', FALSE);
        $this->db->where('id', $transaksi->buku_id);
        $this->db->update('buku');

        $this->db->trans_complete();

        if ($this->db->trans_status() === FALSE) {
            return ['status' => false, 'message' => 'Gagal memproses pengembalian buku.'];
        }

        return ['status' => true, 'message' => 'Buku berhasil dikembalikan. Terima kasih!'];
    }
}
💡 Catatan: Baris komentar (// atau /* */) bersifat opsional/penjelas logika. Tidak wajib diketik semua agar lebih cepat.
2

Controller Manajemen Anggota (admin/Anggota.php)

Lokasi Berkas:

application/controllers/admin/Anggota.php

application/controllers/admin/Anggota.php PHP
<?php
defined('BASEPATH') OR exit('No direct script access allowed');

/**
 * Admin Anggota Controller
 * CRUD Data Anggota (Siswa)
 */
class Anggota extends CI_Controller {

    public function __construct() {
        parent::__construct();
        check_admin();
        $this->load->model('User_model');
        $this->load->model('Transaksi_model');
        $this->load->library('pagination');
    }

    /**
     * Daftar Anggota Siswa
     */
    public function index() {
        $search = $this->input->get('keyword', TRUE);
        $status = $this->input->get('status', TRUE);

        $limit = 10;
        $page = $this->input->get('page') ? (int) $this->input->get('page') : 1;
        $offset = ($page - 1) * $limit;

        $total_rows = $this->User_model->count_all('user', $search, $status);

        // Pagination setup
        $config['base_url'] = site_url('admin/anggota');
        $config['total_rows'] = $total_rows;
        $config['per_page'] = $limit;
        $config['page_query_string'] = TRUE;
        $config['query_string_segment'] = 'page';
        $config['reuse_query_string'] = TRUE;

        $config['full_tag_open'] = '<nav><ul class="pagination pagination-sm mb-0">';
        $config['full_tag_close'] = '</ul></nav>';
        $config['first_link'] = '«';
        $config['first_tag_open'] = '<li class="page-item">';
        $config['first_tag_close'] = '</li>';
        $config['last_link'] = '»';
        $config['last_tag_open'] = '<li class="page-item">';
        $config['last_tag_close'] = '</li>';
        $config['next_link'] = '›';
        $config['next_tag_open'] = '<li class="page-item">';
        $config['next_tag_close'] = '</li>';
        $config['prev_link'] = '‹';
        $config['prev_tag_open'] = '<li class="page-item">';
        $config['prev_tag_close'] = '</li>';
        $config['cur_tag_open'] = '<li class="page-item active"><span class="page-link">';
        $config['cur_tag_close'] = '</span></li>';
        $config['num_tag_open'] = '<li class="page-item">';
        $config['num_tag_close'] = '</li>';
        $config['attributes'] = ['class' => 'page-link'];

        $this->pagination->initialize($config);

        $data['title'] = 'Data Anggota - SIPAS';
        $data['anggota'] = $this->User_model->search('user', $search, $status, $limit, $offset);
        $data['pagination'] = $this->pagination->create_links();
        $data['total_rows'] = $total_rows;
        $data['offset'] = $offset;
        $data['limit'] = $limit;
        $data['keyword'] = $search;
        $data['selected_status'] = $status;

        $this->load->view('admin/anggota/index', $data);
    }

    /**
     * Tambah Anggota Siswa
     */
    public function tambah() {
        $this->form_validation->set_rules('nama', 'Nama Lengkap', 'required|trim', [
            'required' => 'Nama lengkap wajib diisi.'
        ]);
        $this->form_validation->set_rules('nis', 'NIS', 'required|trim|is_unique[users.nis]', [
            'required' => 'NIS wajib diisi.',
            'is_unique' => 'NIS sudah digunakan oleh siswa lain.'
        ]);
        $this->form_validation->set_rules('kelas', 'Kelas', 'required|trim', [
            'required' => 'Kelas wajib diisi.'
        ]);
        $this->form_validation->set_rules('username', 'Username', 'required|trim|is_unique[users.username]|min_length[3]', [
            'required' => 'Username wajib diisi.',
            'is_unique' => 'Username sudah digunakan, silakan pilih yang lain.',
            'min_length' => 'Username minimal 3 karakter.'
        ]);
        $this->form_validation->set_rules('password', 'Password', 'required|min_length[5]', [
            'required' => 'Password wajib diisi.',
            'min_length' => 'Password minimal 5 karakter.'
        ]);
        $this->form_validation->set_rules('status', 'Status', 'required|in_list[aktif,nonaktif]');

        if ($this->form_validation->run() === FALSE) {
            $data['title'] = 'Tambah Anggota - SIPAS';
            $this->load->view('admin/anggota/tambah', $data);
        } else {
            $insert_data = [
                'nama'       => $this->input->post('nama', TRUE),
                'nis'        => $this->input->post('nis', TRUE),
                'kelas'      => $this->input->post('kelas', TRUE),
                'username'   => strtolower($this->input->post('username', TRUE)),
                'password'   => password_hash($this->input->post('password'), PASSWORD_BCRYPT),
                'role'       => 'user',
                'status'     => $this->input->post('status', TRUE),
                'created_at' => date('Y-m-d H:i:s'),
                'updated_at' => date('Y-m-d H:i:s')
            ];

            if ($this->User_model->create($insert_data)) {
                $this->session->set_flashdata('success', 'Data anggota berhasil ditambahkan.');
                redirect('admin/anggota');
            } else {
                $this->session->set_flashdata('error', 'Gagal menambahkan data anggota.');
                redirect('admin/anggota/tambah');
            }
        }
    }

    /**
     * Edit Anggota Siswa
     */
    public function edit($id) {
        $anggota = $this->User_model->get_by_id($id);
        if (!$anggota || $anggota->role !== 'user') {
            $this->session->set_flashdata('error', 'Data anggota tidak ditemukan.');
            redirect('admin/anggota');
        }

        $nis_rule = 'required|trim';
        if ($this->input->post('nis') !== $anggota->nis) {
            $nis_rule .= '|is_unique[users.nis]';
        }

        $username_rule = 'required|trim|min_length[3]';
        if ($this->input->post('username') !== $anggota->username) {
            $username_rule .= '|is_unique[users.username]';
        }

        $this->form_validation->set_rules('nama', 'Nama Lengkap', 'required|trim');
        $this->form_validation->set_rules('nis', 'NIS', $nis_rule);
        $this->form_validation->set_rules('kelas', 'Kelas', 'required|trim');
        $this->form_validation->set_rules('username', 'Username', $username_rule);
        $this->form_validation->set_rules('status', 'Status', 'required|in_list[aktif,nonaktif]');

        if (!empty($this->input->post('password'))) {
            $this->form_validation->set_rules('password', 'Password Baru', 'min_length[5]');
        }

        if ($this->form_validation->run() === FALSE) {
            $data['title'] = 'Edit Anggota - SIPAS';
            $data['anggota'] = $anggota;
            $this->load->view('admin/anggota/edit', $data);
        } else {
            $update_data = [
                'nama'       => $this->input->post('nama', TRUE),
                'nis'        => $this->input->post('nis', TRUE),
                'kelas'      => $this->input->post('kelas', TRUE),
                'username'   => strtolower($this->input->post('username', TRUE)),
                'status'     => $this->input->post('status', TRUE),
                'updated_at' => date('Y-m-d H:i:s')
            ];

            // Update password jika diisi
            if (!empty($this->input->post('password'))) {
                $update_data['password'] = password_hash($this->input->post('password'), PASSWORD_BCRYPT);
            }

            if ($this->User_model->update($id, $update_data)) {
                $this->session->set_flashdata('success', 'Data anggota berhasil diperbarui.');
                redirect('admin/anggota');
            } else {
                $this->session->set_flashdata('error', 'Gagal memperbarui data anggota.');
                redirect('admin/anggota/edit/' . $id);
            }
        }
    }

    /**
     * Detail Anggota beserta Aktivitas Peminjamannya
     */
    public function detail($id) {
        $anggota = $this->User_model->get_by_id($id);
        if (!$anggota || $anggota->role !== 'user') {
            $this->session->set_flashdata('error', 'Data anggota tidak ditemukan.');
            redirect('admin/anggota');
        }

        $data['title'] = 'Detail Anggota - ' . $anggota->nama;
        $data['anggota'] = $anggota;
        $data['transaksi'] = $this->Transaksi_model->get_by_user($id, NULL, NULL, 10, 0);

        $this->load->view('admin/anggota/detail', $data);
    }

    /**
     * Hapus Anggota Siswa
     */
    public function hapus($id) {
        $anggota = $this->User_model->get_by_id($id);
        if (!$anggota || $anggota->role !== 'user') {
            $this->session->set_flashdata('error', 'Data anggota tidak ditemukan.');
            redirect('admin/anggota');
        }

        // Cek apakah anggota masih memiliki pinjaman aktif
        if ($this->User_model->has_active_transaction($id)) {
            $this->session->set_flashdata('error', 'Anggota tidak dapat dihapus karena masih meminjam buku yang belum dikembalikan.');
            redirect('admin/anggota');
            return;
        }

        if ($this->User_model->delete($id)) {
            $this->session->set_flashdata('success', 'Data anggota berhasil dihapus.');
        } else {
            $this->session->set_flashdata('error', 'Gagal menghapus anggota.');
        }
        redirect('admin/anggota');
    }
}
💡 Catatan: Baris komentar (// atau /* */) bersifat opsional/penjelas logika. Tidak wajib diketik semua agar lebih cepat.
3

Controller Transaksi Sirkulasi (admin/Transaksi.php)

Lokasi Berkas:

application/controllers/admin/Transaksi.php

application/controllers/admin/Transaksi.php PHP
<?php
defined('BASEPATH') OR exit('No direct script access allowed');

/**
 * Admin Transaksi Controller
 * Monitoring transaksi, filter status, dan proses pengembalian buku
 */
class Transaksi extends CI_Controller {

    public function __construct() {
        parent::__construct();
        check_admin();
        $this->load->model('Transaksi_model');
        $this->load->library('pagination');
    }

    /**
     * Menampilkan daftar transaksi dengan filter & pencarian
     */
    public function index() {
        $search = $this->input->get('keyword', TRUE);
        $status = $this->input->get('status', TRUE);

        $limit = 10;
        $page = $this->input->get('page') ? (int) $this->input->get('page') : 1;
        $offset = ($page - 1) * $limit;

        $total_rows = $this->Transaksi_model->count_all($status, $search);

        // Pagination setup
        $config['base_url'] = site_url('admin/transaksi');
        $config['total_rows'] = $total_rows;
        $config['per_page'] = $limit;
        $config['page_query_string'] = TRUE;
        $config['query_string_segment'] = 'page';
        $config['reuse_query_string'] = TRUE;

        $config['full_tag_open'] = '<nav><ul class="pagination pagination-sm mb-0">';
        $config['full_tag_close'] = '</ul></nav>';
        $config['first_link'] = '«';
        $config['first_tag_open'] = '<li class="page-item">';
        $config['first_tag_close'] = '</li>';
        $config['last_link'] = '»';
        $config['last_tag_open'] = '<li class="page-item">';
        $config['last_tag_close'] = '</li>';
        $config['next_link'] = '›';
        $config['next_tag_open'] = '<li class="page-item">';
        $config['next_tag_close'] = '</li>';
        $config['prev_link'] = '‹';
        $config['prev_tag_open'] = '<li class="page-item">';
        $config['prev_tag_close'] = '</li>';
        $config['cur_tag_open'] = '<li class="page-item active"><span class="page-link">';
        $config['cur_tag_close'] = '</span></li>';
        $config['num_tag_open'] = '<li class="page-item">';
        $config['num_tag_close'] = '</li>';
        $config['attributes'] = ['class' => 'page-link'];

        $this->pagination->initialize($config);

        $data['title'] = 'Data Transaksi - SIPAS';
        $data['transaksi'] = $this->Transaksi_model->get_all($limit, $offset, $status, $search);
        $data['pagination'] = $this->pagination->create_links();
        $data['total_rows'] = $total_rows;
        $data['offset'] = $offset;
        $data['limit'] = $limit;
        $data['keyword'] = $search;
        $data['selected_status'] = $status ? $status : 'semua';

        $this->load->view('admin/transaksi/index', $data);
    }

    /**
     * Detail Transaksi
     */
    public function detail($id) {
        $transaksi = $this->Transaksi_model->get_by_id($id);
        if (!$transaksi) {
            $this->session->set_flashdata('error', 'Data transaksi tidak ditemukan.');
            redirect('admin/transaksi');
        }

        $data['title'] = 'Detail Transaksi ' . $transaksi->kode_transaksi;
        $data['transaksi'] = $transaksi;
        $this->load->view('admin/transaksi/detail', $data);
    }

    /**
     * Admin memproses pengembalian buku siswa
     */
    public function proses_kembali($id) {
        $result = $this->Transaksi_model->proses_pengembalian($id);

        if ($result['status']) {
            $this->session->set_flashdata('success', $result['message']);
        } else {
            $this->session->set_flashdata('error', $result['message']);
        }

        redirect('admin/transaksi/detail/' . $id);
    }
}
💡 Catatan: Baris komentar (// atau /* */) bersifat opsional/penjelas logika. Tidak wajib diketik semua agar lebih cepat.
← Modul 05: Admin Buku Modul 07: Portal Siswa →