Initial commit

This commit is contained in:
root
2026-05-26 08:07:45 +00:00
commit a234e55e64
4263 changed files with 855927 additions and 0 deletions
+168
View File
@@ -0,0 +1,168 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Accounts extends CI_Controller {
public function __construct()
{
parent::__construct();
$this->load->model('DynamicModel', 'dm');
if (!$this->session->userdata('logged_in')) {
redirect('auth');
}
}
public function index()
{
if($this->session->userdata('role') != 'Admin'){
show_error('Akses ditolak', 403);
}
$data = [
"active_menu" => "accounts",
"parent_accounts" => $this->db->get('accounts')->result()
];
$this->load->view('partials/header', $data);
$this->load->view('accounts/layout', $data);
$this->load->view('partials/footer');
}
public function get_data()
{
$this->dm->setTable('accounts')
->setColumn(
[null,'kode_akun','nama_akun','tipe','posisi','kategori',null],
['kode_akun','nama_akun','tipe'],
['kode_akun'=>'ASC']
);
$list = $this->dm->get_datatables();
$data = [];
$no = $_POST['start'];
foreach ($list as $row) {
$no++;
$data[] = [
$no,
$row->kode_akun,
$row->nama_akun,
ucfirst($row->tipe),
ucfirst($row->posisi),
ucfirst($row->kategori),
'
<button class="btn btn-sm btn-warning btn-edit" data-id="'.$row->id.'">Edit</button>
<button class="btn btn-sm btn-danger btn-delete" data-id="'.$row->id.'">Delete</button>
'
];
}
echo json_encode([
"draw" => $_POST['draw'],
"recordsTotal" => $this->dm->count_all(),
"recordsFiltered" => $this->dm->count_filtered(),
"data" => $data
]);
}
// ================= SAVE
public function save()
{
$tipe = $this->input->post('tipe');
$data = [
'kode_akun' => $this->input->post('kode_akun'),
'nama_akun' => $this->input->post('nama_akun'),
'tipe' => $tipe,
'posisi' => in_array($tipe, ['asset','expense']) ? 'debit' : 'kredit',
'kategori' => in_array($tipe, ['asset','liability','equity']) ? 'neraca' : 'laba_rugi',
'parent_id' => $this->input->post('parent_id') ?: NULL
];
$insert = $this->dm->setTable('accounts')->insert($data);
if($insert){
log_activity(
'Accounts',
'create',
'Menambah akun: '.$data['kode_akun'].' - '.$data['nama_akun']
);
}
echo json_encode([
'status' => $insert ? true : false,
'message' => $insert ? 'Data berhasil disimpan' : 'Gagal simpan'
]);
}
// ================= UPDATE
public function update()
{
$id = $this->input->post('id');
$tipe = $this->input->post('tipe');
$data = [
'kode_akun' => $this->input->post('kode_akun'),
'nama_akun' => $this->input->post('nama_akun'),
'tipe' => $tipe,
'posisi' => in_array($tipe, ['asset','expense']) ? 'debit' : 'kredit',
'kategori' => in_array($tipe, ['asset','liability','equity']) ? 'neraca' : 'laba_rugi',
'parent_id' => $this->input->post('parent_id') ?: NULL
];
$update = $this->dm->setTable('accounts')->update($id, $data);
if($update){
log_activity(
'Accounts',
'update',
'Update akun ID '.$id.' menjadi: '.$data['kode_akun'].' - '.$data['nama_akun']
);
}
echo json_encode([
'status' => $update ? true : false,
'message' => $update ? 'Data berhasil diupdate' : 'Gagal update'
]);
}
public function detail($id)
{
$data = $this->dm->setTable('accounts')->find($id);
echo json_encode($data);
}
public function delete($id)
{
// cek apakah jadi parent
$child = $this->db->where('parent_id',$id)->count_all_results('accounts');
if($child > 0){
echo json_encode([
'status'=>false,
'message'=>'Tidak bisa hapus, akun punya child'
]);
return;
}
$delete = $this->dm->setTable('accounts')->delete($id);
if($delete){
log_activity(
'Accounts',
'delete',
'Menghapus akun ID '.$id,
'success'
);
}
echo json_encode([
'status' => $delete ? true : false,
'message' => $delete ? 'Data berhasil dihapus' : 'Gagal hapus'
]);
}
}
+343
View File
@@ -0,0 +1,343 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Asset extends CI_Controller {
public function __construct(){
parent::__construct();
$this->load->database();
}
public function index(){
$data = ["active_menu" => "asset"];
$this->load->view('partials/header', $data);
$this->load->view('asset/layout');
$this->load->view('partials/footer');
}
// ================= GET DATA =================
public function get_data(){
$data = $this->db
->select('assets.*, lokasi_asset.nama as nama_lokasi')
->from('assets')
->join('lokasi_asset', 'lokasi_asset.id = assets.lokasi_asset_id', 'left')
->order_by('assets.id','DESC')
->get()
->result();
$no = 1;
$result = [];
foreach($data as $d){
$result[] = [
$no++,
$d->tanggal_perolehan,
$d->kode_asset ?? '-',
$d->nama_asset,
$d->nama_lokasi ?? '-',
number_format($d->nilai_perolehan),
$d->masa_manfaat . ' bln',
number_format($d->penyusutan_per_bulan),
$d->created_at,
'
<button class="btn btn-sm btn-info btn-detail mb-1 ml-1" data-id="'.$d->id.'"><i class="bi bi-eye"></i></button>
<button class="btn btn-sm btn-warning btn-edit mb-1 ml-1" data-id="'.$d->id.'">Edit</button>
<button class="btn btn-sm btn-danger btn-delete mb-1 ml-1" data-id="'.$d->id.'">Hapus</button>
'
];
}
echo json_encode(['data'=>$result]);
}
// ================= DETAIL ASSET + HISTORY =================
public function detail($id)
{
$data = $this->db
->select('a.*, la.nama AS nama_lokasi') // ambil field tambahan dari lokasi_asset
->from('assets a')
->join('lokasi_asset la', 'a.lokasi_asset_id = la.id', 'left')
->where('a.id', $id)
->get()
->row();
echo json_encode($data);
}
public function get_history($asset_id){
$data = $this->db
->where('asset_id', $asset_id)
->order_by('created_at', 'DESC')
->get('asset_mutations')
->result();
echo json_encode($data);
}
// ================= SAVE MANUAL (1 UNIT) =================
public function save(){
$harga = preg_replace('/\D/','',$this->input->post('harga'));
$residu = preg_replace('/\D/','',$this->input->post('residu'));
$masa = (int)$this->input->post('masa');
$account_debit_id = (int)$this->input->post('account_asset');
$account_kredit_id = (int)$this->input->post('account_kas');
$lokasi_asset_id = (int)$this->input->post('lokasi_asset');
$tanggal = $this->input->post('tanggal');
$nama_asset = $this->input->post('nama_asset');
$keterangan = $this->input->post('keterangan');
$nilai_perolehan = $harga * 1;
$penyusutan = ($harga - $residu) / ($masa ?: 1);
$this->db->trans_start();
$data = [
'kode_asset' => 'AST-'.time(),
'nama_asset' => $nama_asset,
'harga_per_unit' => $harga,
'nilai_perolehan' => $nilai_perolehan,
'tanggal_perolehan' => $tanggal,
'masa_manfaat' => $masa,
'nilai_residu' => $residu,
'penyusutan_per_bulan' => $penyusutan,
'nilai_buku' => $nilai_perolehan,
'sumber' => 'manual',
'account_debit_id' => $account_debit_id,
'account_kredit_id' => $account_kredit_id,
'lokasi_asset_id' => $lokasi_asset_id,
'keterangan' => $keterangan
];
$this->db->insert('assets',$data);
$asset_id = $this->db->insert_id();
// Journal
$this->db->insert('journals',[
'tanggal' => $tanggal,
'no_ref' => 'AST-'.date('YmdHis').'-'.$asset_id,
'keterangan' => 'Penambahan Aset '.$nama_asset,
'ref_type' => 'assets',
'ref_id' => $asset_id,
'created_by' => $this->session->userdata('user_id')
]);
$jid = $this->db->insert_id();
$this->db->insert('journal_details',[
'journal_id' => $jid,
'account_id' => $account_debit_id,
'debit' => $nilai_perolehan,
'kredit' => 0
]);
$this->db->insert('journal_details',[
'journal_id' => $jid,
'account_id' => $account_kredit_id,
'debit' => 0,
'kredit' => $nilai_perolehan
]);
// MUTATION PEROLEHAN
$this->db->insert('asset_mutations',[
'asset_id' => $asset_id,
'tipe' => 'perolehan',
'nilai' => $nilai_perolehan,
'keterangan' => 'Perolehan manual: '.$keterangan
]);
$this->db->trans_complete();
echo json_encode(['status'=>true]);
}
// ================= SAVE FROM STOCK =================
public function save_from_stock(){
$item_id = $this->input->post('item_id');
$account_debit_id = (int)$this->input->post('account_asset');
$lokasi_asset_id = (int)$this->input->post('s_lokasi_asset');
$masa = (int)$this->input->post('s_masa');
$residu = preg_replace('/\D/','',$this->input->post('s_residu'));
$tanggal = $this->input->post('s_tanggal');
$item = $this->db->get_where('items',['id'=>$item_id])->row();
if(!$item){
echo json_encode(['status'=>false,'message'=>'Item tidak ditemukan']);
return;
}
if($item->stok < 1){
echo json_encode(['status'=>false,'message'=>'Stok tidak cukup']);
return;
}
$harga = $item->harga_beli;
$nama_asset = $item->nama_barang;
$nilai_perolehan = $harga;
$penyusutan = ($harga - $residu) / ($masa ?: 1);
$this->db->trans_start();
// INSERT ASSET
$this->db->insert('assets',[
'item_id' => $item_id,
'kode_asset' => 'AST-'.time(),
'nama_asset' => $nama_asset,
'harga_per_unit' => $harga,
'nilai_perolehan' => $nilai_perolehan,
'tanggal_perolehan' => $tanggal,
'masa_manfaat' => $masa,
'nilai_residu' => $residu,
'penyusutan_per_bulan' => $penyusutan,
'nilai_buku' => $nilai_perolehan,
'sumber' => 'gudang',
'account_debit_id' => $account_debit_id,
'account_kredit_id' => 21, // Persediaan
'lokasi_asset_id' => $lokasi_asset_id
]);
$asset_id = $this->db->insert_id();
// Journal
$this->db->insert('journals',[
'tanggal' => $tanggal,
'no_ref' => 'AST-'.date('YmdHis').'-'.$asset_id,
'keterangan' => 'Penambahan Aset dari Gudang '.$nama_asset,
'ref_type' => 'assets',
'ref_id' => $asset_id,
'created_by' => $this->session->userdata('user_id')
]);
$jid = $this->db->insert_id();
// Debit Asset
$this->db->insert('journal_details',[
'journal_id' => $jid,
'account_id' => $account_debit_id,
'debit' => $nilai_perolehan,
'kredit' => 0
]);
// Kredit Persediaan
$this->db->insert('journal_details',[
'journal_id' => $jid,
'account_id' => 21,
'debit' => 0,
'kredit' => $nilai_perolehan
]);
// UPDATE STOK ITEM (kurangi 1)
$this->db->where('id', $item_id)
->set('stok', 'stok-1', FALSE)
->update('items');
// MUTATION DARI GUDANG
$this->db->insert('asset_mutations',[
'asset_id' => $asset_id,
'tipe' => 'perolehan',
'nilai' => $nilai_perolehan,
'keterangan' => 'Perolehan dari gudang: '.$nama_asset
]);
$this->db->trans_complete();
echo json_encode(['status'=>true]);
}
// ================= UPDATE =================
public function update(){
$id = $this->input->post('id');
$nama_asset = $this->input->post('nama_asset');
$lokasi_asset_id = (int)$this->input->post('lokasi_asset');
$keterangan = $this->input->post('keterangan');
$this->db->trans_start();
$data = [
'nama_asset' => $nama_asset,
'lokasi_asset_id' => $lokasi_asset_id,
'keterangan' => $keterangan
];
$this->db->where('id',$id);
$this->db->update('assets',$data);
// MUTATION EDIT
$this->db->insert('asset_mutations',[
'asset_id' => $id,
'tipe' => 'penambahan',
'nilai' => 0,
'keterangan' => 'Edit asset: '.$nama_asset
]);
$this->db->trans_complete();
echo json_encode(['status'=>true]);
}
// ================= DELETE =================
public function delete(){
$id = $this->input->post('id');
$alasan = $this->input->post('alasan');
$keterangan = $this->input->post('keterangan');
$asset = $this->db->get_where('assets',['id'=>$id])->row();
$this->db->trans_start();
// Hapus journal
$journals = $this->db->get_where('journals', ['ref_type'=>'assets', 'ref_id'=>$id])->result();
foreach($journals as $j){
$this->db->delete('journal_details', ['journal_id'=>$j->id]);
$this->db->delete('journals', ['id'=>$j->id]);
}
// Hapus asset
$this->db->delete('assets',['id'=>$id]);
// MUTATION DELETE
$this->db->insert('asset_mutations',[
'asset_id' => $id,
'tipe' => 'pengurangan',
'nilai' => $asset->nilai_buku,
'keterangan' => 'Penghapusan ('.$alasan.'): '.$keterangan
]);
$this->db->trans_complete();
echo json_encode(['status'=>true]);
}
// ================= GET DATA SELECT =================
public function get_accounts_kas(){
$data = $this->db
->where_in('sub_tipe', ['kas', 'hutang'])
->order_by('kode_akun', 'ASC')
->get('accounts')
->result();
echo json_encode($data);
}
public function get_accounts_asset(){
$data = $this->db
->group_start()
->like('nama_akun', 'Aset', 'after')
->group_end()
->order_by('kode_akun', 'ASC')
->get('accounts')
->result();
echo json_encode($data);
}
public function get_items(){
$data = $this->db
->where('stok > 0')
->get('items')
->result();
echo json_encode($data);
}
public function get_lokasi_asset(){
$data = $this->db
->get('lokasi_asset')
->result();
echo json_encode($data);
}
}
@@ -0,0 +1,85 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class AttendanceWebhook extends CI_Controller {
public function __construct()
{
parent::__construct();
date_default_timezone_set('Asia/Jakarta');
}
public function cdata()
{
// =========================
// AMBIL SN MESIN
// =========================
$sn = $this->input->get('SN');
// =========================
// RAW BODY DARI MESIN
// =========================
$body = file_get_contents('php://input');
// =========================
// LOG DIRECTORY
// =========================
$logDir = APPPATH . 'logs/fingerprint/';
if (!is_dir($logDir)) {
mkdir($logDir, 0777, true);
}
// =========================
// FILE LOG
// =========================
$logFile = $logDir . date('Y-m-d') . '.log';
// =========================
// FORMAT LOG HEADER
// =========================
$logText = "\n==================================================\n";
$logText .= "TIME : " . date('Y-m-d H:i:s') . "\n";
$logText .= "SN : " . $sn . "\n";
$logText .= "IP : " . $this->input->ip_address() . "\n";
$logText .= "DATA : \n";
// =========================
// PARSE BARIS ABSENSI
// =========================
$lines = explode("\n", trim($body));
foreach ($lines as $line) {
$line = trim($line);
if (empty($line)) {
continue;
}
$logText .= $line . "\n";
// contoh parsing
$data = explode("\t", $line);
/*
hasil biasanya:
[0] => PIN/User ID
[1] => Datetime
[2] => Status
*/
}
// =========================
// SIMPAN LOG
// =========================
file_put_contents($logFile, $logText, FILE_APPEND);
// =========================
// RESPONSE KE MESIN
// =========================
echo "OK";
}
}
@@ -0,0 +1,65 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Attendancemonitoring extends CI_Controller {
public function __construct()
{
parent::__construct();
$this->load->database();
}
public function index()
{
$data = [
"active_menu" => "attendance_monitoring"
];
$this->load->view('partials/header', $data);
$this->load->view('employees/attendance_monitoring');
$this->load->view('partials/footer');
}
public function get_data()
{
$date = $this->input->get('date');
if(!$date){
$date = date('Y-m-d');
}
$data = $this->db->query("
SELECT
a.*,
e.employee_code,
e.full_name,
s.shift_name
FROM k_attendances a
LEFT JOIN k_employees e ON e.id = a.employee_id
LEFT JOIN k_shifts s ON s.id = a.shift_id
WHERE a.attendance_date = '$date'
ORDER BY a.checkin_time DESC
")->result();
$rows = [];
$no = 1;
foreach($data as $d){
$rows[] = [
$no++,
$d->employee_code,
$d->full_name,
$d->shift_name,
$d->checkin_time,
$d->checkout_time,
$d->attendance_status
];
}
echo json_encode([
'data' => $rows
]);
}
}
+87
View File
@@ -0,0 +1,87 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Auth extends CI_Controller {
public function __construct()
{
parent::__construct();
}
public function index()
{
if ($this->session->userdata('logged_in')) {
redirect('dashboard');
}
$this->load->view('auth/login');
}
public function process_login()
{
$username = $this->input->post('username');
$password = $this->input->post('password');
$user = $this->db->get_where('v_users', ['username' => $username])->row();
if ($user && password_verify($password, $user->password)) {
// Decode permissions
$permissions = json_decode($user->permissions, true);
if (!is_array($permissions)) {
$permissions = [];
}
$session_data = [
'user_id' => $user->id,
'nama' => $user->nama,
'username' => $user->username,
'role' => $user->nama_role,
'permissions' => $permissions,
'logged_in' => TRUE
];
$this->session->set_userdata($session_data);
// 🔥 LOG LOGIN BERHASIL
log_activity(
'Auth',
'login',
'User '.$user->username.' berhasil login',
'success'
);
redirect('dashboard');
} else {
// 🔥 LOG LOGIN GAGAL
log_activity(
'Auth',
'login_failed',
'Login gagal untuk username: '.$username,
'failed'
);
$this->session->set_flashdata('error', 'Username atau Password salah!');
redirect('auth');
}
}
public function logout()
{
$username = $this->session->userdata('username');
// 🔥 LOG LOGOUT (SEBELUM DESTROY SESSION)
log_activity(
'Auth',
'logout',
'User '.$username.' logout',
'success'
);
$this->session->sess_destroy();
redirect('auth');
}
}
?>
+290
View File
@@ -0,0 +1,290 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Basejurnal extends CI_Controller {
public function __construct()
{
parent::__construct();
$this->load->model('DynamicModel', 'dm');
if (!$this->session->userdata('logged_in')) {
redirect('auth');
}
}
// ================= INDEX
public function index()
{
if($this->session->userdata('role') != 'Admin'){
show_error('Akses ditolak', 403);
}
$data = [
"active_menu" => "basejurnal"
];
$this->load->view('partials/header', $data);
$this->load->view('basejurnal/layout', $data);
$this->load->view('partials/footer');
}
// ================= DATATABLE
public function get_data()
{
$this->dm->setTable('base_journal')
->setColumn(
[null,'kode','nama','deskripsi','is_active',null],
['kode','nama'],
['id'=>'DESC']
);
$list = $this->dm->get_datatables();
$data = [];
$no = $_POST['start'];
foreach ($list as $row) {
$no++;
$status = $row->is_active ? 'Aktif' : 'Nonaktif';
$data[] = [
$no,
$row->kode,
$row->nama,
$row->deskripsi,
$status,
'
<button class="btn btn-sm btn-warning btn-edit" data-id="'.$row->id.'">Edit</button>
<button class="btn btn-sm btn-danger btn-delete" data-id="'.$row->id.'">Hapus</button>
'
];
}
echo json_encode([
"draw" => $_POST['draw'],
"recordsTotal" => $this->dm->count_all(),
"recordsFiltered" => $this->dm->count_filtered(),
"data" => $data
]);
}
public function save()
{
$this->db->trans_start();
$kode = trim($this->input->post('kode'));
$nama = trim($this->input->post('nama'));
if(empty($kode) || empty($nama)){
echo json_encode([
'status' => false,
'message' => 'Kode & Nama wajib diisi'
]);
return;
}
$header = [
'kode' => $kode,
'nama' => $nama,
'deskripsi' => $this->input->post('deskripsi'),
'is_active' => 1
];
$this->dm->setTable('base_journal')->insert($header);
$base_id = $this->db->insert_id();
$accounts = $this->input->post('account_id');
$posisi = $this->input->post('posisi');
if(!empty($accounts)){
$detail = [];
foreach ($accounts as $i => $acc) {
if(empty($acc)) continue;
$detail[] = [
'base_journal_id' => $base_id,
'account_id' => $acc,
'posisi' => $posisi[$i] ?? 'debit',
'urutan' => $i + 1
];
}
if(!empty($detail)){
$this->db->insert_batch('base_journal_detail', $detail);
}
}
$this->db->trans_complete();
if (!$this->db->trans_status()) {
echo json_encode([
'status' => false,
'message' => 'Gagal simpan data'
]);
return;
}
log_activity(
'base_journal',
'create',
'Membuat base journal kode: '.$kode,
'success'
);
echo json_encode([
'status' => true,
'message' => 'Data berhasil disimpan'
]);
}
public function update()
{
$id = $this->input->post('id');
if(empty($id)){
echo json_encode([
'status' => false,
'message' => 'ID tidak valid'
]);
return;
}
$this->db->trans_start();
$header = [
'kode' => trim($this->input->post('kode')),
'nama' => trim($this->input->post('nama')),
'deskripsi' => $this->input->post('deskripsi'),
];
$this->dm->setTable('base_journal')->update($id, $header);
// 🔥 DELETE OLD DETAIL
$this->db->where('base_journal_id', $id)->delete('base_journal_detail');
// 🔥 INSERT NEW DETAIL
$accounts = $this->input->post('account_id');
$posisi = $this->input->post('posisi');
if(!empty($accounts)){
$detail = [];
foreach ($accounts as $i => $acc) {
if(empty($acc)) continue;
$detail[] = [
'base_journal_id' => $id,
'account_id' => $acc,
'posisi' => $posisi[$i] ?? 'debit',
'urutan' => $i + 1
];
}
if(!empty($detail)){
$this->db->insert_batch('base_journal_detail', $detail);
}
}
$this->db->trans_complete();
if (!$this->db->trans_status()) {
echo json_encode([
'status' => false,
'message' => 'Gagal update data'
]);
return;
}
log_activity(
'base_journal',
'update',
'Mengubah base journal ID: '.$id.' (kode: '.$header['kode'].')',
'success'
);
echo json_encode([
'status' => true,
'message' => 'Data berhasil diupdate'
]);
}
// ================= DETAIL
public function detail($id)
{
$header = $this->dm->setTable('base_journal')->find($id);
$detail = $this->db
->select('d.*, a.nama_akun as account_name')
->from('base_journal_detail d')
->join('accounts a','a.id = d.account_id','left') // <-- GANTI DI SINI
->where('d.base_journal_id',$id)
->order_by('d.urutan','ASC')
->get()
->result();
echo json_encode([
'header' => $header,
'detail' => $detail
]);
}
public function delete($id = null)
{
// set response JSON
$this->output->set_content_type('application/json');
if (empty($id)) {
echo json_encode([
'status' => false,
'message' => 'ID tidak valid'
]);
return;
}
$this->db->trans_start();
// hapus detail dulu
$this->db->where('base_journal_id', $id)->delete('base_journal_detail');
// hapus header
$delete = $this->dm->setTable('base_journal')->delete($id);
$this->db->trans_complete();
if (!$this->db->trans_status() || !$delete) {
echo json_encode([
'status' => false,
'message' => 'Gagal hapus data'
]);
return;
}
// ================= LOG ACTIVITY =================
log_activity(
'base_journal',
'delete',
'Menghapus base journal ID: '.$id,
'success'
);
echo json_encode([
'status' => true,
'message' => 'Data berhasil dihapus'
]);
}
public function get_list_account()
{
$data = $this->db->select('id, nama_akun, kode_akun')
->from('accounts') // sesuaikan tabel kamu
->order_by('kode_akun','ASC')
->get()
->result();
echo json_encode($data);
}
}
+108
View File
@@ -0,0 +1,108 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Bukubesar extends CI_Controller {
public function __construct()
{
parent::__construct();
$this->load->model('DynamicModel', 'dm');
if (!$this->session->userdata('logged_in')) {
redirect('auth');
}
}
public function index()
{
if($this->session->userdata('role') != 'Admin'){
show_error('Akses ditolak', 403);
}
$data = ["active_menu" => "buku_besar"];
$this->load->view('partials/header', $data);
$this->load->view('buku_besar/layout');
$this->load->view('partials/footer');
}
// =========================
// GET ACCOUNTS (FILTER)
// =========================
public function get_accounts()
{
$data = $this->dm->setTable('accounts')
->order_by('kode_akun','ASC')
->get();
echo json_encode($data);
}
// =========================
// GET DATA BUKU BESAR
// =========================
public function get_data()
{
$account_id = $this->input->get('account_id');
$start = $this->input->get('start_date');
$end = $this->input->get('end_date');
$this->db->select('
journals.tanggal,
journals.no_ref,
journals.keterangan,
journal_details.debit,
journal_details.kredit,
accounts.nama_akun,
accounts.tipe
');
$this->db->from('journal_details');
$this->db->join('journals','journals.id = journal_details.journal_id');
$this->db->join('accounts','accounts.id = journal_details.account_id');
if ($account_id) {
$this->db->where('journal_details.account_id', $account_id);
}
if ($start && $end) {
$this->db->where('journals.tanggal >=', $start);
$this->db->where('journals.tanggal <=', $end);
}
// 🔥 FILTER DEBIT/KREDIT TIDAK BOLEH 0 SEMUA
$this->db->group_start();
$this->db->where('journal_details.debit >', 0);
$this->db->or_where('journal_details.kredit >', 0);
$this->db->group_end();
$this->db->order_by('journals.tanggal','ASC');
$rows = $this->db->get()->result();
// =========================
// HITUNG SALDO BERJALAN
// =========================
$saldo = 0;
$result = [];
foreach ($rows as $r) {
// LOGIKA SALDO BERDASARKAN TIPE AKUN
if (in_array($r->tipe, ['asset','expense'])) {
$saldo += ($r->debit - $r->kredit);
} else {
$saldo += ($r->kredit - $r->debit);
}
$result[] = [
'tanggal' => $r->tanggal,
'no_ref' => $r->no_ref,
'keterangan' => $r->keterangan,
'debit' => $r->debit,
'kredit' => $r->kredit,
'saldo' => $saldo
];
}
echo json_encode($result);
}
}
+165
View File
@@ -0,0 +1,165 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Cameraqrscan extends CI_Controller {
public function __construct()
{
parent::__construct();
$this->load->model('DynamicModel', 'dm');
// Cek apakah user sudah login
if (!$this->session->userdata('logged_in')) {
redirect('login');
}
}
public function index($type = 'masuk')
{
// pastikan hanya masuk/keluar
$type = strtolower($type);
if (!in_array($type, ['masuk', 'keluar'])) {
show_404();
return;
}
$data = [
"active_menu" => "",
"scan_type" => $type // passing ke view
];
$this->load->view('partials/header', $data);
$this->load->view('scan/qr_scan', $data);
$this->load->view('partials/footer');
}
public function get_data()
{
$qr_code = $this->input->post('qr_code');
$scan_type = $this->input->post('scan_type'); // masuk / keluar
if (!$qr_code) {
echo json_encode(['status' => false, 'message' => 'QR Code tidak ditemukan']);
return;
}
// ============================================================
// AMBIL DATA SISWA
// ============================================================
$siswa = $this->db->get_where('v_siswa', ['qr_id' => $qr_code])->row_array();
if (!$siswa) {
echo json_encode([
'status' => false,
'message' => 'Data siswa tidak ditemukan'
]);
return;
}
$id_siswa = $siswa['id'];
$id_kelas = $siswa['id_kelas'];
$id_kamar = $siswa['id_kamar'];
// ============================================================
// CEGAH DUPLIKAT 5 MENIT (PostgreSQL)
// ============================================================
// Hitung waktu 5 menit yang lalu
$datetimeNow = date('Y-m-d H:i:s');
$datetime5MenitLalu = date('Y-m-d H:i:s', strtotime('-5 minutes'));
$cek = $this->db->query("
SELECT id
FROM kunjungan
WHERE id_siswa = ?
AND created_at >= ?
ORDER BY created_at DESC
LIMIT 1
", [$id_siswa, $datetime5MenitLalu])->row_array();
if ($cek) {
echo json_encode([
'status' => true,
'data' => $siswa,
'scan_type' => $scan_type,
'message' => "Sudah tercatat dalam 5 menit terakhir"
]);
return;
}
// ============================================================
// AMBIL TEMPLATE PESAN
// ============================================================
$template = $this->db->get_where('template_pesan', ['jenis' => $scan_type])->row_array();
if (!$template) {
echo json_encode([
'status' => false,
'message' => 'Template pesan tidak ditemukan => ' . $scan_type
]);
return;
}
$pesan = $template['isi_template'];
$pesanType = $template['type'];
// ============================================================
// REPLACE TEMPLATE {{column}}
// ============================================================
foreach ($siswa as $key => $value) {
$pesan = str_replace('{{'.$key.'}}', $value, $pesan);
}
// Tambahan otomatis
$pesan = str_replace('{{tanggal}}', date('Y-m-d'), $pesan);
$pesan = str_replace('{{jam}}', date('H:i:s'), $pesan);
// ============================================================
// INSERT KUNJUNGAN
// ============================================================
$this->db->insert('kunjungan', [
'id_siswa' => $id_siswa,
'type' => $scan_type,
'id_users' => $this->session->userdata('user_id'),
'id_kelas' => $id_kelas,
'id_kamar' => $id_kamar,
'created_at' => date('Y-m-d H:i:s'),
'media_type' => 'qr'
]);
$new_id = $this->db->insert_id();
// ============================================================
// INSERT NOTIFIKASI WA
// ============================================================
$this->db->insert('notifikasi', [
'type' => $pesanType,
'pesan' => $pesan,
'status' => 'pending',
'nomor_whatsapp' => $siswa['nomor_whatsapp'],
'created_at' => date('Y-m-d H:i:s'),
]);
// ============================================================
// RESPONSE
// ============================================================
$info = ($scan_type === 'masuk')
? "Berhasil scan MASUK"
: "Berhasil scan KELUAR";
// tampilkan data lengkap dari v_kunjungan
$dataSiswa = $this->db->get_where('v_kunjungan_detail', ['id' => $new_id])->row();
echo json_encode([
'status' => true,
'data' => $dataSiswa,
'scan_type' => $scan_type,
'message' => $info
]);
}
}
+140
View File
@@ -0,0 +1,140 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Cronjob extends CI_Controller {
public function __construct()
{
parent::__construct();
$this->load->database();
// ONLY CLI ACCESS
if (!$this->input->is_cli_request()) {
exit('No direct access allowed');
}
}
public function generate_insight()
{
$period = date('Y-m');
// =====================================================
// 🔥 REPLACE MODE (hapus data lama dulu)
// =====================================================
$this->db->where('period', $period);
$this->db->delete('ai_insights');
// =====================================================
// 1. FINANCE - OVERDUE INVOICE
// =====================================================
$overdue = $this->db->query("
SELECT COUNT(*) as total
FROM invoices
WHERE status != 'paid'
AND jatuh_tempo < CURDATE()
AND deleted_at IS NULL
")->row()->total;
if ($overdue > 0) {
$this->db->insert('ai_insights', [
'type' => 'finance',
'severity' => 'warning',
'message' => "$overdue invoice sudah melewati jatuh tempo",
'meta' => json_encode([
'overdue_count' => (int)$overdue
]),
'period' => $period
]);
}
// =====================================================
// 2. FINANCE - CASHFLOW BULAN INI
// =====================================================
$cash = $this->db->query("
SELECT COALESCE(SUM(jd.debit),0) as cash_in
FROM journal_details jd
JOIN journals j ON j.id = jd.journal_id
WHERE MONTH(j.tanggal) = MONTH(CURDATE())
AND YEAR(j.tanggal) = YEAR(CURDATE())
")->row()->cash_in;
if ($cash > 0) {
$this->db->insert('ai_insights', [
'type' => 'finance',
'severity' => ($cash > 10000000) ? 'info' : 'warning',
'message' => "Cashflow bulan ini Rp " . number_format($cash),
'meta' => json_encode([
'cash_in' => (float)$cash
]),
'period' => $period
]);
}
// =====================================================
// 3. STOCK - KRITIS
// =====================================================
$stocks = $this->db->query("
SELECT
i.kode_id,
k.nama_barang,
SUM(i.stok) as total_stok,
k.limit_stock
FROM items i
JOIN kode_barang k ON k.id = i.kode_id
GROUP BY i.kode_id, k.nama_barang, k.limit_stock
HAVING total_stok <= k.limit_stock
")->result();
foreach ($stocks as $s) {
$this->db->insert('ai_insights', [
'type' => 'stock',
'severity' => 'critical',
'message' => "Stok {$s->nama_barang} kritis ({$s->total_stok})",
'meta' => json_encode([
'kode_id' => $s->kode_id,
'item' => $s->nama_barang,
'stok' => (int)$s->total_stok,
'limit' => (int)$s->limit_stock
]),
'period' => $period
]);
}
// =====================================================
// 4. CUSTOMER - PAYMENT RISK
// =====================================================
$lateCustomers = $this->db->query("
SELECT
c.nama,
COUNT(i.id) as total_invoice
FROM invoices i
JOIN customers c ON c.id = i.customer_id
WHERE i.status != 'paid'
AND i.jatuh_tempo < CURDATE()
GROUP BY i.customer_id
HAVING total_invoice > 2
")->result();
foreach ($lateCustomers as $c) {
$this->db->insert('ai_insights', [
'type' => 'customer',
'severity' => 'warning',
'message' => "Customer {$c->nama} memiliki {$c->total_invoice} invoice overdue",
'meta' => json_encode([
'customer' => $c->nama,
'overdue_invoice' => (int)$c->total_invoice
]),
'period' => $period
]);
}
// =====================================================
// RESPONSE
// =====================================================
echo json_encode([
'status' => true,
'message' => 'AI Insight generated (REPLACE MODE)',
'period' => $period
]);
}
}
+150
View File
@@ -0,0 +1,150 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Customers extends CI_Controller {
public function __construct()
{
parent::__construct();
$this->load->model('DynamicModel', 'dm');
if (!$this->session->userdata('logged_in')) {
redirect('auth');
}
}
public function index()
{
$data = ["active_menu" => "customers"];
$this->load->view('partials/header', $data);
$this->load->view('customers/layout');
$this->load->view('partials/footer');
}
// =========================
// DATATABLE
// =========================
public function get_data()
{
$this->dm->setTable('customers')
->setColumn(
[null,'nama','alamat','telp','email',null],
['nama','alamat','telp','email'],
['id'=>'DESC']
);
$list = $this->dm->get_datatables();
$data = [];
$no = $_POST['start'];
foreach ($list as $row) {
$no++;
$aksi = '
<button class="btn btn-sm btn-warning btn-edit" data-id="'.$row->id.'">Edit</button>
<button class="btn btn-sm btn-danger btn-delete" data-id="'.$row->id.'">Delete</button>
';
$data[] = [
$no,
$row->nama,
$row->alamat,
$row->telp,
$row->email,
$aksi
];
}
echo json_encode([
"draw" => $_POST['draw'],
"recordsTotal" => $this->dm->count_all(),
"recordsFiltered" => $this->dm->count_filtered(),
"data" => $data
]);
}
// =========================
// SAVE
// =========================
public function save()
{
$data = [
'nama' => $this->input->post('nama'),
'alamat' => $this->input->post('alamat'),
'telp' => $this->input->post('telp'),
'email' => $this->input->post('email')
];
if (!$data['nama']) {
echo json_encode(['status'=>false,'message'=>'Nama wajib diisi']);
return;
}
$this->dm->setTable('customers')->insert($data);
// ================= LOG ACTIVITY =================
log_activity(
'customers',
'create',
'Menambahkan customer: '.$data['nama'],
'success'
);
echo json_encode(['status'=>true,'message'=>'Data berhasil disimpan']);
}
// =========================
// DETAIL
// =========================
public function detail($id)
{
$data = $this->dm->setTable('customers')->get_by_id($id);
echo json_encode($data);
}
// =========================
// UPDATE
// =========================
public function update()
{
$id = $this->input->post('id');
$data = [
'nama' => $this->input->post('nama'),
'alamat' => $this->input->post('alamat'),
'telp' => $this->input->post('telp'),
'email' => $this->input->post('email')
];
$this->dm->setTable('customers')->update($id, $data);
// ================= LOG ACTIVITY =================
log_activity(
'customers',
'update',
'Mengubah customer ID: '.$id.' ('.$data['nama'].')',
'success'
);
echo json_encode(['status'=>true,'message'=>'Data berhasil diupdate']);
}
// =========================
// DELETE
// =========================
public function delete($id)
{
$this->dm->setTable('customers')->delete($id);
// ================= LOG ACTIVITY =================
log_activity(
'customers',
'delete',
'Menghapus customer: '.($customer->nama ?? 'ID '.$id),
'success'
);
echo json_encode(['status'=>true,'message'=>'Data berhasil dihapus']);
}
}
+279
View File
@@ -0,0 +1,279 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Dashboard extends CI_Controller {
public function __construct()
{
parent::__construct();
$this->load->library('session');
// Cek apakah user sudah login
if (!$this->session->userdata('logged_in')) {
redirect('auth');
}
}
public function index()
{
$this->db->select('
accounts.id,
accounts.kode_akun,
accounts.nama_akun,
accounts.tipe,
COALESCE(SUM(journal_details.debit),0) as debit,
COALESCE(SUM(journal_details.kredit),0) as kredit
');
$this->db->from('accounts');
$this->db->join('journal_details','accounts.id = journal_details.account_id','left');
$this->db->where('accounts.kategori','laba_rugi');
$this->db->where('accounts.is_active',1);
$this->db->group_by('accounts.id');
$this->db->order_by('accounts.kode_akun','ASC');
$rows = $this->db->get()->result();
$revenue = 0;
$expense = 0;
foreach ($rows as $r) {
// 🔥 NORMALISASI SALDO BERDASARKAN POSISI
if ($r->tipe == 'revenue') {
$saldo = $r->kredit - $r->debit;
$revenue += $saldo;
}
if ($r->tipe == 'expense') {
$saldo = $r->debit - $r->kredit;
$expense += $saldo;
}
}
$summary = [
'total_revenue' => $revenue,
'total_expense' => $expense,
'net_profit' => $revenue - $expense
];
$today = date('Y-m');
$ai_insight = $this->db
->select('type, severity, message, meta, created_at')
->where('period', $today)
->order_by("
CASE severity
WHEN 'critical' THEN 1
WHEN 'warning' THEN 2
WHEN 'info' THEN 3
END
", '', false)
->order_by('created_at', 'DESC')
->limit(20)
->get('ai_insights')
->result();
$data = [
"active_menu" => "dashboard",
"summary" => $summary,
"ai_insight" => $ai_insight
];
$this->load->view('partials/header', $data);
$this->load->view('dashboard/layout', $data);
$this->load->view('partials/footer');
}
public function get_activity()
{
$this->db->select('activity_logs.*, users.nama');
$this->db->from('activity_logs');
$this->db->join('users','users.id = activity_logs.user_id','left');
if ($this->session->userdata('role') != 'Admin') {
$this->db->where('user_id',$this->session->userdata('user_id'));
}
$this->db->order_by('created_at','DESC');
$this->db->limit(10);
$data = $this->db->get()->result();
echo json_encode($data);
}
public function log_activity()
{
$data = [
"active_menu" => "dashboard"
];
$this->load->view('partials/header', $data);
$this->load->view('dashboard/activity_logs', $data);
$this->load->view('partials/footer');
}
public function get_activity_datatable()
{
$this->load->database();
$draw = $this->input->post('draw');
$start = $this->input->post('start');
$length = $this->input->post('length');
$search = $this->input->post('search')['value'] ?? '';
// =========================
// BASE QUERY
// =========================
$this->db->select('
activity_logs.*,
users.nama
');
$this->db->from('activity_logs');
$this->db->join('users', 'users.id = activity_logs.user_id', 'left');
// =========================
// SEARCH
// =========================
if (!empty($search)) {
$this->db->group_start();
$this->db->like('activity_logs.module', $search);
$this->db->or_like('activity_logs.action', $search);
$this->db->or_like('activity_logs.description', $search);
$this->db->or_like('activity_logs.status', $search);
$this->db->or_like('users.nama', $search);
$this->db->group_end();
}
// =========================
// COUNT FILTERED
// =========================
$filtered_query = clone $this->db;
$recordsFiltered = $filtered_query->count_all_results('', false);
// =========================
// ORDER
// =========================
$this->db->order_by('activity_logs.id', 'DESC');
// =========================
// LIMIT
// =========================
if ($length != -1) {
$this->db->limit($length, $start);
}
$query = $this->db->get();
$data = $query->result();
// =========================
// TOTAL RECORD
// =========================
$recordsTotal = $this->db->count_all('activity_logs');
// =========================
// FORMAT OUTPUT
// =========================
$result = [];
foreach ($data as $row) {
$result[] = [
'created_at' => $row->created_at,
'nama' => $row->nama ?? 'System',
'module' => $row->module,
'action' => $row->action,
'description' => $row->description,
'status' => $row->status,
'ip_address' => $row->ip_address
];
}
echo json_encode([
"draw" => intval($draw),
"recordsTotal" => $recordsTotal,
"recordsFiltered" => $recordsFiltered,
"data" => $result
]);
}
public function get_chart()
{
$year = $this->input->get('year');
// 🔥 DEFAULT: tahun berjalan
if (empty($year)) {
$year = date('Y');
}
$this->db->select('
journals.tanggal,
accounts.tipe,
SUM(journal_details.debit) as total_debit,
SUM(journal_details.kredit) as total_kredit
');
$this->db->from('journal_details');
$this->db->join('journals', 'journals.id = journal_details.journal_id', 'left');
$this->db->join('accounts', 'accounts.id = journal_details.account_id', 'left');
$this->db->where('accounts.kategori', 'laba_rugi');
$this->db->where('accounts.is_active', 1);
// 🔥 pakai tahun default / filter
$this->db->where('YEAR(journals.tanggal)', $year);
$this->db->group_by(['journals.tanggal', 'accounts.tipe']);
$this->db->order_by('journals.tanggal', 'ASC');
$rows = $this->db->get()->result();
$map = [];
foreach ($rows as $r) {
$date = $r->tanggal;
if (!isset($map[$date])) {
$map[$date] = [
'revenue' => 0,
'expense' => 0
];
}
if ($r->tipe == 'revenue') {
$map[$date]['revenue'] += ($r->total_kredit - $r->total_debit);
}
if ($r->tipe == 'expense') {
$map[$date]['expense'] += ($r->total_debit - $r->total_kredit);
}
}
$labels = [];
$revenue = [];
$expense = [];
foreach ($map as $date => $val) {
$labels[] = $date;
$revenue[] = (float)$val['revenue'];
$expense[] = (float)$val['expense'];
}
echo json_encode([
'year' => $year, // optional biar debugging enak
'labels' => $labels,
'revenue' => $revenue,
'expense' => $expense
]);
}
public function get_years()
{
$this->db->select('DISTINCT YEAR(tanggal) as year');
$this->db->from('journals');
$this->db->order_by('year', 'DESC');
$data = $this->db->get()->result();
echo json_encode($data);
}
}
+450
View File
@@ -0,0 +1,450 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Employees extends CI_Controller {
public function __construct()
{
parent::__construct();
$this->load->database();
$this->load->helper(['url', 'file']);
date_default_timezone_set('Asia/Jakarta');
}
// =========================================================
// INDEX
// =========================================================
public function index()
{
$data = [
"active_menu" => "employees"
];
$data['departments'] = $this->db
->where('is_active',1)
->order_by('department_name','ASC')
->get('k_departments')
->result();
$data['positions'] = $this->db
->where('is_active',1)
->order_by('position_name','ASC')
->get('k_positions')
->result();
$data['branches'] = $this->db
->order_by('branch_name','ASC')
->get('k_branches')
->result();
$this->load->view('partials/header', $data);
$this->load->view('employees/employees', $data);
$this->load->view('partials/footer');
}
// =========================================================
// GET ALL
// =========================================================
public function getAll()
{
try {
$data = $this->db
->select("
e.*,
d.department_name,
p.position_name,
b.branch_name
")
->from('k_employees e')
->join(
'k_departments d',
'd.id = e.department_id',
'left'
)
->join(
'k_positions p',
'p.id = e.position_id',
'left'
)
->join(
'k_branches b',
'b.id = e.branch_id',
'left'
)
->order_by('e.department_id','ASC')
->get()
->result();
return $this->json([
'status' => true,
'data' => $data
]);
} catch (Exception $e) {
return $this->json([
'status' => false,
'message' => $e->getMessage(),
'data' => []
]);
}
}
// =========================================================
// DETAIL
// =========================================================
public function detail($id = null)
{
try {
$data = $this->db
->select("
e.*,
d.department_name,
p.position_name,
b.branch_name
")
->from('k_employees e')
->join(
'k_departments d',
'd.id = e.department_id',
'left'
)
->join(
'k_positions p',
'p.id = e.position_id',
'left'
)
->join(
'k_branches b',
'b.id = e.branch_id',
'left'
)
->where('e.id',$id)
->get()
->row();
if (!$data) {
return $this->json([
'status' => false,
'message' => 'Data tidak ditemukan'
]);
}
return $this->json([
'status' => true,
'data' => $data
]);
} catch (Exception $e) {
return $this->json([
'status' => false,
'message' => $e->getMessage()
]);
}
}
// =========================================================
// STORE
// =========================================================
public function store()
{
try {
if (empty(trim($this->input->post('full_name')))) {
return $this->json([
'status' => false,
'message'=> 'Nama wajib diisi'
]);
}
if (empty($this->input->post('join_date'))) {
return $this->json([
'status' => false,
'message'=> 'Tanggal masuk wajib diisi'
]);
}
$employee_code = $this->generateEmployeeCode();
$photo = $this->uploadPhoto();
$data = $this->prepareData();
$data['employee_code'] = $employee_code;
$data['photo'] = $photo;
$this->db->insert('k_employees',$data);
return $this->json([
'status' => true,
'message' => 'Berhasil simpan data'
]);
} catch (Exception $e) {
return $this->json([
'status' => false,
'message' => $e->getMessage()
]);
}
}
// =========================================================
// UPDATE
// =========================================================
public function update($id = null)
{
try {
$employee = $this->db
->get_where('k_employees',['id'=>$id])
->row();
if (!$employee) {
return $this->json([
'status' => false,
'message' => 'Data tidak ditemukan'
]);
}
$data = $this->prepareData();
if (!empty($_FILES['photo_file']['name'])) {
$photo = $this->uploadPhoto();
if (
!empty($employee->photo)
&&
file_exists(FCPATH . $employee->photo)
) {
unlink(FCPATH . $employee->photo);
}
$data['photo'] = $photo;
}
$this->db
->where('id',$id)
->update('k_employees',$data);
return $this->json([
'status' => true,
'message' => 'Berhasil update data'
]);
} catch (Exception $e) {
return $this->json([
'status' => false,
'message' => $e->getMessage()
]);
}
}
// =========================================================
// DELETE
// =========================================================
public function delete($id = null)
{
try {
$employee = $this->db
->get_where('k_employees',['id'=>$id])
->row();
if (!$employee) {
return $this->json([
'status' => false,
'message' => 'Data tidak ditemukan'
]);
}
if (
!empty($employee->photo)
&&
file_exists(FCPATH . $employee->photo)
) {
unlink(FCPATH . $employee->photo);
}
$this->db->delete('k_employees',[
'id'=>$id
]);
return $this->json([
'status' => true,
'message' => 'Berhasil hapus data'
]);
} catch (Exception $e) {
return $this->json([
'status' => false,
'message' => $e->getMessage()
]);
}
}
// =========================================================
// GENERATE CODE
// =========================================================
private function generateEmployeeCode()
{
$last = $this->db
->order_by('id','DESC')
->get('k_employees')
->row();
$number = $last ? $last->id + 1 : 1;
return 'RJN-' . str_pad($number,5,'0',STR_PAD_LEFT);
}
// =========================================================
// UPLOAD PHOTO
// =========================================================
private function uploadPhoto()
{
if (empty($_FILES['photo_file']['name'])) {
return null;
}
$path = FCPATH . 'uploads/karyawan/';
if (!is_dir($path)) {
mkdir($path,0777,true);
}
$config['upload_path'] = './uploads/karyawan/';
$config['allowed_types'] = 'jpg|jpeg|png|webp';
$config['max_size'] = 2048;
$config['encrypt_name'] = true;
$this->load->library('upload',$config);
if (!$this->upload->do_upload('photo_file')) {
throw new Exception(
strip_tags(
$this->upload->display_errors()
)
);
}
$upload = $this->upload->data();
return 'uploads/karyawan/' . $upload['file_name'];
}
// =========================================================
// PREPARE DATA
// =========================================================
private function prepareData()
{
return [
'nik' => trim($this->input->post('nik')),
'full_name' => trim(
$this->input->post('full_name')
),
'gender' => $this->input->post('gender'),
'birth_place' => trim(
$this->input->post('birth_place')
),
'birth_date' => $this->input->post('birth_date'),
'phone' => trim(
$this->input->post('phone')
),
'email' => trim(
$this->input->post('email')
),
'address' => trim(
$this->input->post('address')
),
'join_date' => $this->input->post('join_date'),
'resign_date' => $this->input->post('resign_date'),
'employment_status' => $this->input->post('employment_status'),
'marital_status' => $this->input->post('marital_status'),
'religion' => trim(
$this->input->post('religion')
),
'department_id' => $this->input->post('department_id'),
'position_id' => $this->input->post('position_id'),
'branch_id' => $this->input->post('branch_id'),
'basic_salary' => $this->input->post('basic_salary') ?: 0,
'bank_name' => trim(
$this->input->post('bank_name')
),
'bank_account_number' => trim(
$this->input->post('bank_account_number')
),
'bank_account_name' => trim(
$this->input->post('bank_account_name')
),
'bpjs_kesehatan_number' => trim(
$this->input->post('bpjs_kesehatan_number')
),
'bpjs_ketenagakerjaan_number' => trim(
$this->input->post('bpjs_ketenagakerjaan_number')
),
'fingerprint_user_id' => trim(
$this->input->post('fingerprint_user_id')
),
'is_active' => $this->input->post('is_active') ?: 1
];
}
// =========================================================
// JSON
// =========================================================
private function json($data)
{
return $this->output
->set_content_type('application/json')
->set_output(json_encode($data));
}
}
+188
View File
@@ -0,0 +1,188 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Generateinvoice extends CI_Controller {
public function generate($invoice_id)
{
// ❗ WAJIB: hindari output lain sebelum PDF
ob_end_clean();
// =========================
// 🔥 AMBIL DATA PERUSAHAAN
// =========================
$invp = $this->db->get('pengaturan')->row_array(); // 🔥 FIX: tidak pakai GET server_id
if(!$invp){
show_error('Data perusahaan tidak ditemukan');
}
$nama = $invp['nama_perusahaan'];
$alamat = $invp['alamat_perusahaan'];
$telepon = $invp['telepon_perusahaan'];
$Keterangan = $invp['catatan_perusahaan'];
$Pembuat = $invp['nama_bendahara'];
$logo = base_url('uploads/img/logo-ljn.png');
// =========================
// 🔥 GET INVOICE + CUSTOMER
// =========================
$invi = $this->db->select('invoices.*, customers.nama, customers.alamat, customers.telp')
->from('invoices')
->join('customers', 'customers.id = invoices.customer_id', 'left')
->where('invoices.id', $invoice_id)
->get()
->row_array();
// =========================
// VALIDASI
// =========================
if(!$invi){
show_error('Invoice tidak ditemukan');
}
// =========================
// MAPPING DATA
// =========================
$customerAddress = $invi['alamat'] ?? '-';
$customerName = $invi['nama'] ?? '-';
$customerNumber = $invi['telp'] ?? '-';
$invoiceNumber = $invi['no_invoice'] ?? '-';
$invoiceDate = $invi['tanggal'] ?? '-'; // 🔥 sesuaikan field kamu
$invoiceLimit = $invi['jatuh_tempo'] ?? '-'; // 🔥 sesuaikan juga
// =========================
// 🔥 DETAIL ITEM
// =========================
$header = ['No','Tanggal','Item','Deskripsi','Qty','Harga', 'Subtotal'];
$data = [];
// 🔥 NOTE: sesuaikan tabel kamu (invoice_details / invoice_list)
// $detail = $this->db->get_where('invoice_details', [
// 'invoice_id' => $invoice_id
// ])->result_array();
$detail = $this->db
->where('invoice_id', $invoice_id)
->order_by('tanggal', 'ASC')
->get('invoice_details')
->result_array();
$no = 1;
foreach($detail as $d){
$data[] = [
$no++,
$d['tanggal'],
$d['nama_item'],
$d['keterangan'],
$d['qty'],
number_format($d['harga']),
number_format($d['subtotal'])
];
}
// =========================
// 🔥 TOTAL
// =========================
$total = (float)$invi['total'];
$this->db->select_sum('sisa_piutang');
$this->db->where('customer_id', $invi['customer_id']);
$this->db->where('status !=', 'paid');
$this->db->where('id !=', $invoice_id); // exclude invoice sekarang
$tunggakan = $this->db->get('invoices')->row()->sisa_piutang ?? 0;
// Ngambil Data Tunggakan bulan lalu jika ada.
$keteranganlain = 'Akumulasi Tunggakan Invoice Bulan Lalu';
$nom_lainnya = $tunggakan;
$subtotal = number_format($total);
$lainnya = number_format($nom_lainnya);
$totalAmount = number_format($total + $nom_lainnya);
$terbilang = trim($this->terbilang($total + $nom_lainnya)).' rupiah';
$filename = 'INV-'.$customerName.'-'.$invoiceNumber.'.pdf';
// =========================
// 🔥 LOAD FPDF
// =========================
require_once(APPPATH.'third_party/fpdf/fpdf.php');
require_once(APPPATH.'libraries/PDF_Invoice.php');
$pdf = new PDF();
// 🔥 FIX PATH LOGO (WAJIB)
$logo_path = FCPATH.'img/user/'.$logo;
if(!file_exists($logo_path)){
$logo_path = ''; // biar gak error kalau logo tidak ada
}
$pdf->setHeaderData($nama, $alamat, $telepon, $logo);
$pdf->AddPage();
$pdf->SetFont('Arial','',12);
$pdf->CustomerDetails(
$customerName,
$customerAddress,
$invoiceNumber,
$invoiceDate,
$customerNumber,
$invoiceLimit
);
$pdf->InvoiceTable($header, $data);
$pdf->Total($subtotal,$lainnya,$totalAmount,$terbilang,$keteranganlain);
$pdf->Keterangan($Keterangan);
$pdf->Pembuat($Pembuat);
// =========================
// 🔥 OUTPUT LANGSUNG (TANPA SIMPAN)
// =========================
$pdf->Output('I', $filename);
// =========================
// LOG ACTIVITY
// =========================
log_activity(
'invoice',
'generate',
'Generate invoice #' . $invoiceNumber . ' untuk ' . $customerName,
'success'
);
exit;
}
// =========================
// 🔥 TERBILANG
// =========================
private function terbilang($angka)
{
$angka = abs($angka);
$baca = ["", "satu", "dua", "tiga", "empat", "lima", "enam", "tujuh", "delapan", "sembilan", "sepuluh", "sebelas"];
if ($angka < 12) {
return " " . $baca[$angka];
} elseif ($angka < 20) {
return $this->terbilang($angka - 10) . " belas";
} elseif ($angka < 100) {
return $this->terbilang(intval($angka / 10)) . " puluh" . $this->terbilang($angka % 10);
} elseif ($angka < 200) {
return " seratus" . $this->terbilang($angka - 100);
} elseif ($angka < 1000) {
return $this->terbilang(intval($angka / 100)) . " ratus" . $this->terbilang($angka % 100);
} elseif ($angka < 2000) {
return " seribu" . $this->terbilang($angka - 1000);
} elseif ($angka < 1000000) {
return $this->terbilang(intval($angka / 1000)) . " ribu" . $this->terbilang($angka % 1000);
} elseif ($angka < 1000000000) {
return $this->terbilang(intval($angka / 1000000)) . " juta" . $this->terbilang($angka % 1000000);
}
return "";
}
}
@@ -0,0 +1,768 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Generateschedule extends CI_Controller {
public function __construct()
{
parent::__construct();
$this->load->database();
date_default_timezone_set('Asia/Jakarta');
}
// =====================================================
// INDEX
// =====================================================
public function index()
{
$data = [
"active_menu" => "generate_schedule"
];
// ============================================
// POSITIONS
// ============================================
$data['positions'] = $this->db
->where('is_active',1)
->order_by('position_name','ASC')
->get('k_positions')
->result();
// ============================================
// SHIFTS
// ============================================
$data['shifts'] = $this->db
->order_by('shift_code','ASC')
->get('k_shifts')
->result();
$this->load->view('partials/header', $data);
$this->load->view('employees/generate_schedule', $data);
$this->load->view('partials/footer');
}
// =====================================================
// CALENDAR DATA
// =====================================================
public function calendar_data()
{
$month = (int)$this->input->get('month');
$year = (int)$this->input->get('year');
if(empty($month)){
$month = date('n');
}
if(empty($year)){
$year = date('Y');
}
$firstDate = $year . '-'
. str_pad($month,2,'0',STR_PAD_LEFT)
. '-01';
// ============================================
// START MONDAY
// ============================================
$startDate = date(
'Y-m-d',
strtotime('monday this week', strtotime($firstDate))
);
if(date('N', strtotime($firstDate)) == 1){
$startDate = $firstDate;
}
// ============================================
// END SUNDAY
// ============================================
$lastDate = date(
'Y-m-t',
strtotime($firstDate)
);
$endDate = date(
'Y-m-d',
strtotime('sunday this week', strtotime($lastDate))
);
$days = [];
$current = strtotime($startDate);
$end = strtotime($endDate);
while($current <= $end){
$date = date('Y-m-d', $current);
// ========================================
// GET SCHEDULE GROUP BY SHIFT
// ========================================
$this->db->select('
sh.id as shift_id,
sh.shift_name,
p.position_name,
COUNT(s.id) as total_employee
');
$this->db->from('k_employee_shift_schedules s');
$this->db->join(
'k_employees e',
'e.id = s.employee_id',
'left'
);
$this->db->join(
'k_positions p',
'p.id = e.position_id',
'left'
);
$this->db->join(
'k_shifts sh',
'sh.id = s.shift_id',
'left'
);
$this->db->where('DATE(s.start_datetime)', $date);
// ========================================
// GROUPING
// ========================================
$this->db->group_by('sh.id');
$this->db->group_by('p.id');
$this->db->order_by('sh.checkin_time','ASC');
$this->db->order_by('p.position_name','ASC');
$rows = $this->db->get()->result();
// ========================================
// FORMAT GROUPED
// ========================================
$grouped = [];
if($rows){
foreach($rows as $r){
$shiftId = $r->shift_id ?: 0;
if(!isset($grouped[$shiftId])){
$grouped[$shiftId] = [
'shift_id' => $r->shift_id,
'shift_name' => $r->shift_name ?: '-',
'positions' => []
];
}
$grouped[$shiftId]['positions'][] = [
'position_name' => $r->position_name ?: '-',
'total' => (int)$r->total_employee
];
}
}
$days[] = [
'date' => date('d', strtotime($date)),
'month' => date('M', strtotime($date)),
'day_name' => $this->translateDay(
date('l', strtotime($date))
),
'full_date' => tanggal_indo($date),
'full_date_raw' => $date,
'is_current_month' => (
date('m', strtotime($date))
==
str_pad($month,2,'0',STR_PAD_LEFT)
),
'shifts' => array_values($grouped)
];
$current = strtotime('+1 day', $current);
}
echo json_encode([
'days' => $days
]);
}
// =====================================================
// GET EMPLOYEE
// =====================================================
public function get_employees()
{
$position_id = $this->input->get('position_id');
$this->db->select('
e.id,
e.full_name,
e.fingerprint_user_id,
p.position_name
');
$this->db->from('k_employees e');
$this->db->join(
'k_positions p',
'p.id=e.position_id',
'left'
);
$this->db->where('e.is_active',1);
if(!empty($position_id)){
$this->db->where('e.position_id',$position_id);
}
$this->db->order_by('e.full_name','ASC');
$employees = $this->db->get()->result();
echo json_encode([
'data' => $employees
]);
}
// =====================================================
// SAVE MANUAL SCHEDULE
// =====================================================
public function save_manual_schedule()
{
try{
$employee_ids = $this->input->post('employee_ids');
$schedule_date = $this->input->post('schedule_date');
$shift_id = $this->input->post('shift_id');
// ============================================
// VALIDASI
// ============================================
if(empty($employee_ids)){
throw new Exception('Employee belum dipilih');
}
if(empty($schedule_date)){
throw new Exception('Tanggal wajib diisi');
}
if(empty($shift_id)){
throw new Exception('Shift wajib dipilih');
}
// ============================================
// GET SHIFT
// ============================================
$shift = $this->db
->where('id', $shift_id)
->get('k_shifts')
->row();
if(!$shift){
throw new Exception('Shift tidak ditemukan');
}
// ============================================
// BUILD START DATETIME
// ============================================
$start_datetime = date(
'Y-m-d H:i:s',
strtotime(
$schedule_date . ' ' . $shift->checkin_time
)
);
// ============================================
// BUILD END DATETIME
// ============================================
$end_datetime = date(
'Y-m-d H:i:s',
strtotime(
$schedule_date . ' ' . $shift->checkout_time
)
);
// ============================================
// JIKA SHIFT NYEBRANG HARI
// ============================================
$is_cross_day = 0;
if(
strtotime($shift->checkout_time)
<=
strtotime($shift->checkin_time)
){
$end_datetime = date(
'Y-m-d H:i:s',
strtotime('+1 day', strtotime($end_datetime))
);
$is_cross_day = 1;
}
// ============================================
// HITUNG TOTAL JAM KERJA
// ============================================
$work_hours = (
strtotime($end_datetime)
-
strtotime($start_datetime)
) / 3600;
// ============================================
// LOOP EMPLOYEE
// ============================================
foreach($employee_ids as $employee_id){
// ========================================
// CHECK EXIST
// ========================================
$exists = $this->db
->where('employee_id', $employee_id)
->where('schedule_date', $schedule_date)
->get('k_employee_shift_schedules')
->row();
// ========================================
// DATA SAVE
// ========================================
$saveData = [
'employee_id' => $employee_id,
'shift_id' => $shift_id,
'start_datetime' => $start_datetime,
'end_datetime' => $end_datetime,
'work_hours' => $work_hours,
'is_cross_day' => $is_cross_day,
'schedule_source' => 'manual',
'notes' => 'manual planner',
'updated_at' => date('Y-m-d H:i:s')
];
// ========================================
// UPDATE
// ========================================
if($exists){
$this->db
->where('id', $exists->id)
->update(
'k_employee_shift_schedules',
$saveData
);
// ========================================
// INSERT
// ========================================
} else {
$saveData['created_at'] = date('Y-m-d H:i:s');
$this->db->insert(
'k_employee_shift_schedules',
$saveData
);
}
}
echo json_encode([
'status' => true,
'message' => 'Jadwal berhasil disimpan'
]);
} catch(Exception $e){
echo json_encode([
'status' => false,
'message' => $e->getMessage()
]);
}
}
// =====================================================
// TRANSLATE DAY
// =====================================================
private function translateDay($day)
{
$days = [
'Sunday' => 'Minggu',
'Monday' => 'Senin',
'Tuesday' => 'Selasa',
'Wednesday' => 'Rabu',
'Thursday' => 'Kamis',
'Friday' => 'Jumat',
'Saturday' => 'Sabtu'
];
return isset($days[$day])
? $days[$day]
: $day;
}
// =====================================================
// DETAIL SCHEDULE
// =====================================================
public function detail_schedule()
{
$schedule_date = $this->input->get('schedule_date');
if(empty($schedule_date)){
echo json_encode([
'status' => false,
'message'=> 'Tanggal wajib diisi',
'data' => []
]);
return;
}
// ============================================
// GET DATA
// ============================================
$this->db->select('
s.id as schedule_id,
s.employee_id,
s.shift_id,
s.start_datetime,
s.end_datetime,
e.employee_code,
e.full_name,
e.fingerprint_user_id,
p.id as position_id,
p.position_name,
sh.shift_name,
sh.checkin_time,
sh.checkout_time,
sh.color
');
$this->db->from('k_employee_shift_schedules s');
$this->db->join(
'k_employees e',
'e.id=s.employee_id',
'left'
);
$this->db->join(
'k_positions p',
'p.id=e.position_id',
'left'
);
$this->db->join(
'k_shifts sh',
'sh.id=s.shift_id',
'left'
);
// ============================================
// FILTER DATE
// ============================================
$this->db->where(
'DATE(s.start_datetime)',
$schedule_date
);
$this->db->order_by('p.position_name','ASC');
$this->db->order_by('e.full_name','ASC');
$rows = $this->db->get()->result();
// ============================================
// GROUP POSITION
// ============================================
$grouped = [];
if($rows){
foreach($rows as $r){
$position_id = $r->position_id ?: 0;
// ====================================
// CREATE GROUP
// ====================================
if(!isset($grouped[$position_id])){
$grouped[$position_id] = [
'position_id' => $r->position_id,
'position_name' => $r->position_name ?: '-',
'employees' => []
];
}
// ====================================
// EMPLOYEE
// ====================================
$grouped[$position_id]['employees'][] = [
'schedule_id' => $r->schedule_id,
'employee_id' => $r->employee_id,
'full_name' => $r->full_name,
'employee_code' => $r->employee_code,
'fingerprint_user_id' => $r->fingerprint_user_id,
'shift_id' => $r->shift_id,
'shift_name' => $r->shift_name,
'checkin' => $r->checkin_time
? date(
'H:i',
strtotime($r->checkin_time)
)
: '-',
'checkout' => $r->checkout_time
? date(
'H:i',
strtotime($r->checkout_time)
)
: '-',
'start_datetime' => $r->start_datetime,
'end_datetime' => $r->end_datetime,
'color' => $r->color
];
}
}
echo json_encode([
'status' => true,
'data' => array_values($grouped)
]);
}
// =====================================================
// UPDATE SCHEDULE SHIFT
// =====================================================
public function update_schedule_shift()
{
try{
$schedule_id = $this->input->post('schedule_id');
$shift_id = $this->input->post('shift_id');
// ============================================
// VALIDASI
// ============================================
if(empty($schedule_id)){
throw new Exception('Schedule ID kosong');
}
if(empty($shift_id)){
throw new Exception('Shift wajib dipilih');
}
// ============================================
// CHECK SCHEDULE
// ============================================
$schedule = $this->db
->where('id', $schedule_id)
->get('k_employee_shift_schedules')
->row();
if(!$schedule){
throw new Exception('Schedule tidak ditemukan');
}
// ============================================
// CHECK SHIFT
// ============================================
$shift = $this->db
->where('id', $shift_id)
->get('k_shifts')
->row();
if(!$shift){
throw new Exception('Shift tidak ditemukan');
}
// ============================================
// AMBIL TANGGAL
// ============================================
$schedule_date = date(
'Y-m-d',
strtotime($schedule->start_datetime)
);
// ============================================
// BUILD START DATETIME
// ============================================
$start_datetime = date(
'Y-m-d H:i:s',
strtotime(
$schedule_date . ' ' . $shift->checkin_time
)
);
// ============================================
// BUILD END DATETIME
// ============================================
$end_datetime = date(
'Y-m-d H:i:s',
strtotime(
$schedule_date . ' ' . $shift->checkout_time
)
);
// ============================================
// CROSS DAY
// ============================================
$is_cross_day = 0;
if(
strtotime($shift->checkout_time)
<=
strtotime($shift->checkin_time)
){
$end_datetime = date(
'Y-m-d H:i:s',
strtotime('+1 day', strtotime($end_datetime))
);
$is_cross_day = 1;
}
// ============================================
// WORK HOURS
// ============================================
$work_hours = $shift->work_hours;
// ============================================
// UPDATE
// ============================================
$this->db
->where('id', $schedule_id)
->update(
'k_employee_shift_schedules',
[
'shift_id' => $shift_id,
'start_datetime' => $start_datetime,
'end_datetime' => $end_datetime,
'work_hours' => $work_hours,
'is_cross_day' => $is_cross_day,
'updated_at' => date('Y-m-d H:i:s')
]
);
echo json_encode([
'status' => true,
'message' => 'Shift berhasil diupdate'
]);
} catch(Exception $e){
echo json_encode([
'status' => false,
'message' => $e->getMessage()
]);
}
}
// =====================================================
// DELETE SCHEDULE
// =====================================================
public function delete_schedule()
{
try{
$schedule_id = $this->input->post('schedule_id');
// ============================================
// VALIDASI
// ============================================
if(empty($schedule_id)){
throw new Exception('Schedule ID kosong');
}
// ============================================
// CHECK DATA
// ============================================
$schedule = $this->db
->where('id', $schedule_id)
->get('k_employee_shift_schedules')
->row();
if(!$schedule){
throw new Exception('Schedule tidak ditemukan');
}
// ============================================
// DELETE
// ============================================
$this->db
->where('id', $schedule_id)
->delete('k_employee_shift_schedules');
echo json_encode([
'status' => true,
'message' => 'Schedule berhasil dihapus'
]);
} catch(Exception $e){
echo json_encode([
'status' => false,
'message' => $e->getMessage()
]);
}
}
}
+177
View File
@@ -0,0 +1,177 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Holidays extends CI_Controller {
public function __construct()
{
parent::__construct();
$this->load->database();
}
// =========================================
// VIEW
// =========================================
public function index()
{
$data = [
"active_menu" => "holidays"
];
$this->load->view('partials/header', $data);
$this->load->view('employees/holidays');
$this->load->view('partials/footer');
}
// =========================================
// GET DATA
// =========================================
public function get_data()
{
$data = $this->db
->order_by('holiday_date','DESC')
->get('k_holidays')
->result();
$rows = [];
$no = 1;
foreach($data as $d){
$rows[] = [
$no++,
date('d M Y', strtotime($d->holiday_date)),
$d->holiday_name,
$d->is_national == 1
? '<span class="badge bg-success">Nasional</span>'
: '<span class="badge bg-secondary">Perusahaan</span>',
'
<button class="btn btn-warning btn-sm btn-edit"
data-id="'.$d->id.'">
Edit
</button>
<button class="btn btn-danger btn-sm btn-delete"
data-id="'.$d->id.'">
Delete
</button>
'
];
}
echo json_encode([
'data' => $rows
]);
}
// =========================================
// DETAIL
// =========================================
public function detail($id)
{
$data = $this->db
->get_where('k_holidays',['id'=>$id])
->row();
echo json_encode($data);
}
// =========================================
// SAVE
// =========================================
public function save()
{
$holiday_date = $this->input->post('holiday_date');
$holiday_name = $this->input->post('holiday_name');
$is_national = $this->input->post('is_national');
$cek = $this->db
->get_where('k_holidays',[
'holiday_date' => $holiday_date
])
->row();
if($cek){
echo json_encode([
'status' => false,
'message' => 'Tanggal holiday sudah ada'
]);
return;
}
$insert = [
'holiday_date' => $holiday_date,
'holiday_name' => $holiday_name,
'is_national' => $is_national
];
$this->db->insert('k_holidays',$insert);
echo json_encode([
'status' => true,
'message' => 'Holiday berhasil ditambahkan'
]);
}
// =========================================
// UPDATE
// =========================================
public function update()
{
$id = $this->input->post('id');
$holiday_date = $this->input->post('holiday_date');
$holiday_name = $this->input->post('holiday_name');
$is_national = $this->input->post('is_national');
$cek = $this->db
->where('holiday_date',$holiday_date)
->where('id !=',$id)
->get('k_holidays')
->row();
if($cek){
echo json_encode([
'status' => false,
'message' => 'Tanggal holiday sudah ada'
]);
return;
}
$update = [
'holiday_date' => $holiday_date,
'holiday_name' => $holiday_name,
'is_national' => $is_national
];
$this->db
->where('id',$id)
->update('k_holidays',$update);
echo json_encode([
'status' => true,
'message' => 'Holiday berhasil diupdate'
]);
}
// =========================================
// DELETE
// =========================================
public function delete($id)
{
$this->db
->where('id',$id)
->delete('k_holidays');
echo json_encode([
'status' => true,
'message' => 'Holiday berhasil dihapus'
]);
}
}
File diff suppressed because it is too large Load Diff
+795
View File
@@ -0,0 +1,795 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Items extends CI_Controller {
public function __construct()
{
parent::__construct();
if (!$this->session->userdata('logged_in')) {
redirect('auth');
}
}
public function index()
{
$data = ["active_menu" => "items"];
$this->load->view('partials/header', $data);
$this->load->view('items/layout');
$this->load->view('partials/footer');
}
public function draft_items()
{
$data = ["active_menu" => "draft_items"];
$this->load->view('partials/header', $data);
$this->load->view('items/draft_items');
$this->load->view('partials/footer');
}
// =========================
// GET DATA (FIX)
// =========================
public function get_data()
{
$status = $this->input->get('s') ?? 'all';
$allowed = ['draft','active','all'];
if (!in_array($status, $allowed)) {
$status = 'all';
}
$this->db->select('
items.id,
items.nama_barang,
items.kode_detail,
items.harga_beli,
items.harga_jual,
items.tanggal_pembelian,
items.created_at,
items.stok,
items.status,
items.kode_id,
(
SELECT GROUP_CONCAT(DISTINCT w.nama SEPARATOR ", ")
FROM stock_logs sl
LEFT JOIN warehouses w ON sl.warehouse_id = w.id
WHERE sl.item_id = items.id
) as gudang
');
if ($status !== 'all') {
$this->db->where('items.status', $status);
}
// urutan custom
$this->db->order_by('(items.stok = 0)', 'ASC', false);
$this->db->order_by('items.tanggal_pembelian', 'ASC');
$this->db->from('items');
$data = $this->db->get()->result();
// $this->db->select('
// items.id,
// items.nama_barang,
// items.kode_detail,
// items.harga_beli,
// items.harga_jual,
// items.tanggal_pembelian,
// items.created_at,
// items.stok,
// items.status,
// items.kode_id,
// (
// SELECT GROUP_CONCAT(DISTINCT w.nama SEPARATOR ", ")
// FROM stock_logs sl
// LEFT JOIN warehouses w ON sl.warehouse_id = w.id
// WHERE sl.item_id = items.id
// ) as gudang
// ');
// if ($status !== 'all') {
// $this->db->where('items.status', $status);
// }
// // 🔥 filter utama per kode_id
// $this->db->where("
// (items.stok > 0)
// OR
// (
// items.stok = 0
// AND items.tanggal_pembelian = (
// SELECT MAX(i2.tanggal_pembelian)
// FROM items i2
// WHERE i2.stok = 0
// AND i2.kode_id = items.kode_id
// )
// )
// ", null, false);
// // urutan
// $this->db->order_by('items.stok = 0', 'ASC', false);
// $this->db->order_by('items.tanggal_pembelian', 'ASC');
// $this->db->from('items');
// $data = $this->db->get()->result();
$result = [];
$no = 1;
foreach($data as $row){
$btnDetail = '<button class="btn btn-info btn-sm btn-detail m-1" data-id="'.htmlspecialchars($row->id, ENT_QUOTES, 'UTF-8').'">Detail</button>';
$btnEdit = '<button class="btn btn-secondary btn-sm btn-editItem m-1" data-id="'.htmlspecialchars($row->id, ENT_QUOTES, 'UTF-8').'">Edit</button>';
$btnDelete = '<button class="btn btn-danger btn-sm btn-delete m-1" data-id="'.htmlspecialchars($row->id, ENT_QUOTES, 'UTF-8').'">Delete</button>';
if($row->status == 'draft'){
$btnPost = '<button class="btn btn-success btn-sm btn-updatePosting m-1" data-id="'.htmlspecialchars($row->id, ENT_QUOTES, 'UTF-8').'">Posting</button>';
$action = $btnDetail . $btnPost . $btnDelete;
} else {
if($this->session->userdata('role') == 'Admin') {
$btnAdjust = '<button class="btn btn-warning btn-sm btn-adjust m-1" data-id="'.htmlspecialchars($row->id, ENT_QUOTES, 'UTF-8').'">Adjust</button>';
} else {
$btnAdjust = '';
}
$action = $btnDetail . $btnEdit . $btnAdjust . $btnDelete;
}
$result[] = [
$no++,
$row->tanggal_pembelian,
$row->kode_detail,
$row->nama_barang,
$row->gudang ?? '-',
$row->stok,
number_format($row->harga_beli ?? 0, 2),
number_format($row->harga_jual ?? 0, 2),
$row->created_at,
$action
];
}
echo json_encode(["data"=>$result]);
}
// =========================
// DETAIL
// =========================
public function detail_simple($id)
{
echo json_encode(
$this->db->get_where('items',['id'=>$id])->row()
);
}
// =========================
// DETAIL
// =========================
public function detail($id)
{
$item = $this->db->get_where('items',['id'=>$id])->row();
$logs = $this->db
->select('stock_logs.*, warehouses.nama as gudang')
->join('warehouses','warehouses.id = stock_logs.warehouse_id','left')
->where('item_id',$id)
->order_by('stock_logs.id','DESC')
->get('stock_logs')
->result();
echo json_encode([
'item'=>$item,
'logs'=>$logs
]);
}
public function get_accounts()
{
$data = $this->db
->where_in('kode_akun', [101,102,201])
->order_by('kode_akun','ASC')
->get('accounts')
->result();
echo json_encode($data);
}
public function get_accounts_biaya()
{
$data = $this->db
->where(['tipe' => 'expense', 'posisi'=> 'debit', 'kategori'=> 'laba_rugi'])
->order_by('kode_akun','ASC')
->get('accounts')
->result();
echo json_encode($data);
}
// =========================
// SAVE (FIX TOTAL)
// =========================
public function save()
{
$nama_barang = $this->input->post('nama_barang');
$qty = (int)$this->input->post('qty');
$harga_beli = (float)$this->input->post('harga_beli');
$harga_jual = (float)$this->input->post('harga_jual');
$warehouse_id = $this->input->post('warehouse_id');
$account_kas = $this->input->post('account_kas');
$kode_id = $this->input->post('kode_id');
$tanggal_beli = $this->input->post('tanggal_beli');
$status = $this->input->post('status');
// ================= VALIDASI =================
if(!$nama_barang || $qty <= 0 || $harga_beli <= 0 || !$warehouse_id || !$account_kas){
echo json_encode([
'status'=>false,
'message'=>'Data tidak lengkap / tidak valid'
]);
return;
}
// ================= VALIDASI HARGA =================
if($harga_beli > $harga_jual){
echo json_encode([
'status'=>false,
'message'=>'Harga beli tidak boleh lebih besar dari harga jual'
]);
return;
}
$kode = $this->db->get_where('kode_barang',['id'=>$kode_id])->row();
$tanggal_kode = date('dmy', strtotime($tanggal_beli));
$kode_detail = $kode->kode_barang . '-' . $tanggal_kode;
$total = $qty * $harga_beli;
$this->db->trans_start();
// ================= INSERT ITEM =================
$this->db->insert('items',[
'stok' =>$qty,
'kode_detail' =>$kode_detail,
'kode_id' =>$kode_id,
'nama_barang' =>$nama_barang,
'harga_beli' =>$harga_beli,
'harga_jual' =>$harga_jual,
'tanggal_pembelian' => $tanggal_beli,
'status' => $status
]);
$item_id = $this->db->insert_id();
// ================= STOCK MASUK =================
$this->db->insert('stock_logs',[
'item_id'=>$item_id,
'warehouse_id'=>$warehouse_id,
'qty'=>$qty,
'tipe'=>'masuk',
'keterangan'=>'Pembelian awal',
'created_at'=>date('Y-m-d H:i:s')
]);
// ================= JURNAL =================
$no_ref = 'BLI-'.date('Ymd').'-'.$item_id;
$this->db->insert('journals',[
'tanggal'=>$tanggal_beli,
'no_ref'=>$no_ref,
'ref_type'=> 'new_items',
'ref_id'=>$item_id,
'keterangan'=>'Pembelian '.$nama_barang,
'created_by' => $this->session->userdata('user_id')
]);
$jid = $this->db->insert_id();
// 🔥 Debit Persediaan
$this->db->insert('journal_details',[
'journal_id'=>$jid,
'account_id'=>21, // persediaan
'debit'=>$total,
'kredit'=>0
]);
// 🔥 Kredit Kas (DINAMIS)
$this->db->insert('journal_details',[
'journal_id'=>$jid,
'account_id'=>$account_kas,
'debit'=>0,
'kredit'=>$total
]);
$this->db->trans_complete();
// ================= CEK TRANSAKSI =================
if ($this->db->trans_status() === FALSE){
echo json_encode([
'status'=>false,
'message'=>'Gagal simpan data'
]);
} else {
log_activity(
'items',
'create',
'Menambahkan item: ' . $nama_barang,
'success'
);
echo json_encode([
'status'=>true,
'message'=>'Berhasil tambah item'
]);
}
}
// =========================
// UPDATE (FIX AKUN)
// =========================
public function update()
{
$id = $this->input->post('id');
$nama_barang = $this->input->post('nama_barang');
$harga_baru = (float)$this->input->post('harga_beli');
$harga_jual = (float)$this->input->post('harga_jual');
$item = $this->db->get_where('items',['id'=>$id])->row();
if(!$item){
echo json_encode(['status'=>false,'message'=>'Item tidak ditemukan']);
return;
}
$this->db->trans_start();
$this->db->where('id',$id)->update('items',[
'nama_barang'=>$nama_barang,
'harga_beli'=>$harga_baru,
'harga_jual'=>$harga_jual
]);
if($item->harga_beli != $harga_baru){
$stok = $this->db->select('SUM(qty * IF(tipe="masuk",1,-1)) as stok')
->where('item_id',$id)
->get('stock_logs')
->row()->stok ?? 0;
$selisih = ($harga_baru - $item->harga_beli) * $stok;
if($selisih != 0){
$this->db->insert('journals',[
'tanggal'=>date('Y-m-d'),
'no_ref'=>'ADJ-'.date('YmdHis').'-'.$id,
'keterangan'=>'Penyesuaian harga '.$item->nama_barang,
'created_by' => $this->session->userdata('user_id')
]);
$jid = $this->db->insert_id();
if($selisih > 0){
// 🔥 naik → pendapatan
$this->db->insert_batch('journal_details',[
[
'journal_id'=>$jid,
'account_id'=>21,
'debit'=>$selisih,
'kredit'=>0
],
[
'journal_id'=>$jid,
'account_id'=>65, // ✅ BENAR
'debit'=>0,
'kredit'=>$selisih
]
]);
}else{
// 🔥 turun → beban
$this->db->insert_batch('journal_details',[
[
'journal_id'=>$jid,
'account_id'=>64, // ✅ BENAR
'debit'=>abs($selisih),
'kredit'=>0
],
[
'journal_id'=>$jid,
'account_id'=>21,
'debit'=>0,
'kredit'=>abs($selisih)
]
]);
}
}
}
$this->db->trans_complete();
log_activity(
'items',
'update',
'Update item: ' . $nama_barang,
'success'
);
$this->update_item_stok($id);
echo json_encode(['status'=>true,'message'=>'Berhasil update + adjustment']);
}
public function delete($id)
{
// cek item ada
$item = $this->db->get_where('items',['id'=>$id])->row();
if(!$item){
echo json_encode(['status'=>false,'message'=>'Item tidak ditemukan']);
return;
}
// cek ada transaksi stock
$cekStock = $this->db->where('item_id',$id)->count_all_results('stock_logs');
if($cekStock > 1){
echo json_encode([
'status'=>false,
'message'=>'Tidak bisa dihapus, sudah ada transaksi stok'
]);
return;
}
$this->db->trans_start();
// hapus item
$this->db->delete('stock_logs',['item_id'=>$id]);
$this->db->delete('items',['id'=>$id]);
$j = $this->db->get_where('journals', [
'ref_type'=>'new_items',
'ref_id'=>$id
])->result();
foreach($j as $row){
$this->db->delete('journal_details', ['journal_id'=>$row->id]);
$this->db->delete('journals', ['id'=>$row->id]);
}
$this->db->trans_complete();
if ($this->db->trans_status() === FALSE){
echo json_encode(['status'=>false,'message'=>'Gagal hapus']);
} else {
log_activity(
'items',
'delete',
'Hapus item ID: ' . $id,
'success'
);
echo json_encode(['status'=>true,'message'=>'Berhasil dihapus']);
}
}
public function posting()
{
$id = $this->input->post('id');
if(!$id){
echo json_encode([
'status' => false,
'message' => 'ID tidak valid'
]);
return;
}
// cek item ada
$item = $this->db->get_where('items', ['id' => $id])->row();
if(!$item){
echo json_encode([
'status' => false,
'message' => 'Item tidak ditemukan'
]);
return;
}
// cegah double posting
if($item->status == 'active'){
echo json_encode([
'status' => false,
'message' => 'Barang sudah diterima sebelumnya'
]);
return;
}
$this->db->trans_start();
// update status saja
$this->db->where('id', $id);
$this->db->update('items', [
'status' => 'active'
]);
$this->db->trans_complete();
if ($this->db->trans_status() === FALSE){
echo json_encode([
'status' => false,
'message' => 'Gagal memproses data'
]);
} else {
// log aktivitas
log_activity(
'items',
'posting',
'Barang diterima | ID: '.$id.' | Nama: '.$item->nama_barang,
'success'
);
echo json_encode([
'status' => true,
'message' => 'Barang sudah diterima dan stok siap digunakan'
]);
}
}
// =========================
// KELUAR BARANG (FIX TANPA stok column)
// =========================
public function keluarkan()
{
$account_biaya = (int)$this->input->post('account_biaya');
$tanggal_keluar = $this->input->post('tanggal_keluar');
$warehouse_id_keluar = $this->input->post('warehouse_id_keluar');
$barang_id = $this->input->post('barang_id');
$qty_keluar = (int)$this->input->post('qty_keluar');
$keterangan_keluar = $this->input->post('keterangan_keluar');
// =========================
// VALIDASI
// =========================
if(!$barang_id || !$warehouse_id_keluar || $qty_keluar <= 0){
echo json_encode(['status'=>false,'message'=>'Data tidak valid']);
return;
}
// =========================
// CEK ITEM
// =========================
$item = $this->db->get_where('items',['id'=>$barang_id])->row();
if(!$item){
echo json_encode(['status'=>false,'message'=>'Item tidak ditemukan']);
return;
}
// =========================
// HITUNG STOK
// =========================
$stok = $this->db->select('
COALESCE(SUM(qty * IF(tipe="masuk",1,-1)),0) as stok
')
->where('item_id',$barang_id)
->where('warehouse_id',$warehouse_id_keluar)
->get('stock_logs')
->row()->stok;
if($stok < $qty_keluar){
echo json_encode(['status'=>false,'message'=>'Stok tidak cukup']);
return;
}
$nilai = $qty_keluar * (float)$item->harga_beli;
// =========================
// TRANSACTION
// =========================
$this->db->trans_start();
// log keluar
$this->db->insert('stock_logs',[
'item_id'=>$barang_id,
'warehouse_id'=>$warehouse_id_keluar,
'qty'=>$qty_keluar,
'tipe'=>'keluar',
'keterangan'=>$keterangan_keluar
]);
$stock_log_id = $this->db->insert_id();
// jurnal
$this->db->insert('journals',[
'tanggal'=>$tanggal_keluar,
'no_ref'=>'GOUT-'.date('YmdHis').'-'.$barang_id,
'keterangan'=>'Pengeluaran Barang '.$item->nama_barang . ' ( ' . $keterangan_keluar . ' )',
'ref_type' =>'stock_logs',
'ref_id' => $stock_log_id,
'created_by' => $this->session->userdata('user_id')
]);
$jid = $this->db->insert_id();
// Debit biaya / HPP
$this->db->insert('journal_details',[
'journal_id'=>$jid,
'account_id'=>$account_biaya,
'debit'=>$nilai,
'kredit'=>0
]);
// Kredit persediaan
$this->db->insert('journal_details',[
'journal_id'=>$jid,
'account_id'=>21,
'debit'=>0,
'kredit'=>$nilai
]);
$this->db->trans_complete();
// =========================
// CEK TRANSACTION
// =========================
if (!$this->db->trans_status()) {
echo json_encode(['status'=>false,'message'=>'Gagal proses']);
return;
}
log_activity(
'items',
'stock_out',
'Keluar barang: ' . $item->nama_barang . ' qty: ' . $qty_keluar . ' ( ' . $keterangan_keluar . ' )',
'success'
);
$this->update_item_stok($barang_id);
echo json_encode(['status'=>true,'message'=>'Barang berhasil dikeluarkan']);
}
public function list()
{
echo json_encode(
$this->db->get('warehouses')->result()
);
}
public function adjust()
{
$item_id = $this->input->post('item_id');
$qty = (int)$this->input->post('qty');
$warehouse_id = $this->input->post('warehouse_id');
$ket = $this->input->post('keterangan');
if($qty == 0){
echo json_encode(['status'=>false,'message'=>'Qty tidak boleh 0']);
return;
}
$item = $this->db->get_where('items',['id'=>$item_id])->row();
$nilai = abs($qty) * $item->harga_beli;
$this->db->trans_start();
// 🔥 stock log
$this->db->insert('stock_logs',[
'item_id'=>$item_id,
'warehouse_id'=>$warehouse_id,
'qty'=>abs($qty),
'tipe'=> $qty > 0 ? 'masuk' : 'keluar',
'keterangan'=>'Adjustment: '.$ket
]);
// 🔥 jurnal
$this->db->insert('journals',[
'tanggal'=>date('Y-m-d'),
'no_ref'=>'ADJ-'.date('Ymd').'-'.$item_id,
'keterangan'=>'Penyesuaian '.$item->nama_barang,
'created_by' => $this->session->userdata('user_id')
]);
$jid = $this->db->insert_id();
if($qty > 0){
// stok naik
$this->db->insert_batch('journal_details',[
[
'journal_id'=>$jid,
'account_id'=>21, // persediaan
'debit'=>$nilai,
'kredit'=>0
],
[
'journal_id'=>$jid,
'account_id'=>65, // modal / selisih
'debit'=>0,
'kredit'=>$nilai
]
]);
}else{
// stok turun
$this->db->insert_batch('journal_details',[
[
'journal_id'=>$jid,
'account_id'=>64,
'debit'=>$nilai,
'kredit'=>0
],
[
'journal_id'=>$jid,
'account_id'=>21,
'debit'=>0,
'kredit'=>$nilai
]
]);
}
$this->db->trans_complete();
log_activity(
'items',
'adjust',
'Adjustment item ID: ' . $item_id . ' qty: ' . $qty,
'success'
);
$this->update_item_stok($item_id);
echo json_encode(['status'=>true,'message'=>'Adjustment berhasil']);
}
private function update_item_stok($item_id)
{
$stok = $this->db->query("
SELECT COALESCE(SUM(qty * IF(tipe='masuk',1,-1)),0) as stok
FROM stock_logs
WHERE item_id = ?
", [$item_id])->row()->stok;
$this->db->where('id', $item_id)
->update('items', [
'stok' => $stok
]);
}
public function get_items_by_wh_id($warehouse_id)
{
$this->db->select('
items.id,
items.nama_barang,
items.harga_jual,
items.kode_detail,
COALESCE(SUM(
stock_logs.qty * IF(stock_logs.tipe="masuk",1,-1)
),0) as stok
');
$this->db->where('status', 'active');
$this->db->from('items');
$this->db->join('stock_logs',
'stock_logs.item_id = items.id
AND stock_logs.warehouse_id = '.$this->db->escape($warehouse_id),
'left');
$this->db->group_by('items.id');
// ✅ INI KUNCINYA
$this->db->having('stok >', 0);
$data = $this->db->get()->result();
echo json_encode($data);
}
}
+333
View File
@@ -0,0 +1,333 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Jurnal extends CI_Controller {
public function __construct()
{
parent::__construct();
$this->load->model('DynamicModel', 'dm');
if (!$this->session->userdata('logged_in')) {
redirect('auth');
}
}
public function index()
{
// if($this->session->userdata('role') != 'Admin'){
// show_error('Akses ditolak', 403);
// }
$data = ["active_menu" => "jurnal"];
$this->load->view('partials/header', $data);
$this->load->view('jurnal/layout');
$this->load->view('partials/footer');
}
// =========================
// GET DATA DATATABLE (SUDAH ADA TOTAL)
// =========================
public function get_data()
{
$draw = $_POST['draw'];
$start = $_POST['start'];
$length = $_POST['length'];
$search = $_POST['search']['value'];
// ================= TOTAL ALL
$totalAll = $this->db->count_all('journals');
// ================= QUERY FILTERED (HITUNG)
$this->db->from('journals');
if(!empty($search)){
$this->db->group_start();
$this->db->like('no_ref', $search);
$this->db->or_like('keterangan', $search);
$this->db->or_like('tanggal', $search);
$this->db->group_end();
}
$filtered = $this->db->count_all_results();
// ================= QUERY DATA (AMBIL DATA)
$this->db->select('
journals.id,
journals.tanggal,
journals.no_ref,
journals.keterangan,
COALESCE(SUM(journal_details.debit),0) as total_debit,
COALESCE(SUM(journal_details.kredit),0) as total_kredit
');
$this->db->from('journals');
$this->db->join('journal_details','journal_details.journal_id = journals.id','LEFT');
if(!empty($search)){
$this->db->group_start();
$this->db->like('journals.no_ref', $search);
$this->db->or_like('journals.keterangan', $search);
$this->db->or_like('journals.tanggal', $search);
$this->db->group_end();
}
$this->db->group_by('journals.id');
$this->db->order_by('journals.tanggal','DESC');
$this->db->order_by('journals.created_at','DESC');
$this->db->limit($length, $start);
$query = $this->db->get()->result();
// ================= FORMAT
$data = [];
$no = $start;
foreach ($query as $row) {
$no++;
if($this->session->userdata('role') != 'Admin'){
$aksi = '
<button class="btn btn-sm btn-info btn-detail" data-id="'.$row->id.'">Detail</button>
';
}
else {
$aksi = '
<button class="btn btn-sm btn-info btn-detail" data-id="'.$row->id.'">Detail</button>
<button class="btn btn-sm btn-danger btn-delete" data-id="'.$row->id.'">Delete</button>
';
}
$data[] = [
$no,
$row->tanggal,
$row->no_ref,
$row->keterangan,
number_format($row->total_debit,0,',','.'),
number_format($row->total_kredit,0,',','.'),
$aksi
];
}
echo json_encode([
"draw" => $draw,
"recordsTotal" => $totalAll,
"recordsFiltered" => $filtered,
"data" => $data
]);
}
// =========================
// GET ACCOUNTS
// =========================
public function get_accounts()
{
$data = $this->db->order_by('kode_akun','ASC')->get('accounts')->result();
echo json_encode($data);
}
// =========================
// SAVE
// =========================
public function save()
{
$tanggal = $this->input->post('tanggal');
$no_ref = 'JR-' . date('YmdHis');
$keterangan = $this->input->post('keterangan');
$accounts = $this->input->post('account_id');
$debit = $this->input->post('debit');
$kredit = $this->input->post('kredit');
if (array_sum($debit) != array_sum($kredit)) {
echo json_encode([
'status'=>false,
'message'=>'Debit & Kredit tidak balance!'
]);
return;
}
$this->db->trans_start();
$this->db->insert('journals', [
'tanggal' => $tanggal,
'no_ref' => $no_ref,
'keterangan' => $keterangan,
'created_by' => $this->session->userdata('user_id')
]);
$journal_id = $this->db->insert_id();
for ($i=0; $i<count($accounts); $i++) {
if ($accounts[$i] == '') continue;
$this->db->insert('journal_details', [
'journal_id' => $journal_id,
'account_id' => $accounts[$i],
'debit' => $debit[$i] ?: 0,
'kredit' => $kredit[$i] ?: 0
]);
}
$this->db->trans_complete();
if ($this->db->trans_status() === FALSE) {
echo json_encode([
'status'=>false,
'message'=>'Gagal simpan jurnal'
]);
return;
}
// ================= LOG ACTIVITY =================
log_activity(
'Jurnal',
'create',
'Membuat jurnal No Ref: '.$no_ref,
'success'
);
echo json_encode([
'status'=>true,
'message'=>'Jurnal berhasil disimpan'
]);
}
// =========================
// DETAIL
// =========================
public function detail($id)
{
$header = $this->db->get_where('journals',['id'=>$id])->row();
$detail = $this->db
->select('accounts.kode_akun, accounts.nama_akun, journal_details.*')
->from('journal_details')
->join('accounts','accounts.id = journal_details.account_id')
->where('journal_id',$id)
->get()->result();
// print_r($header);
$userid = $header->created_by;
$user = $this->db
->select('nama')
->where('id', $userid)
->get('users')
->row();
echo json_encode([
'header'=>$header,
'detail'=>$detail,
'user' =>$user
]);
}
// =========================
// DELETE
// =========================
public function delete($id)
{
$journal = $this->db->get_where('journals', ['id' => $id])->row();
// VALIDASI
if (!$journal) {
echo json_encode([
'status' => false,
'message' => 'Jurnal tidak ditemukan'
]);
return;
}
$this->db->trans_start();
// =========================
// HANDLE STOCK LOGS
// =========================
if ($journal->ref_type == 'stock_logs') {
$stock = $this->db
->select('item_id, warehouse_id')
->where('id', $journal->ref_id)
->get('stock_logs')
->row();
if ($stock) {
// hapus log stok
$this->db->delete('stock_logs', ['id' => $journal->ref_id]);
// update stok ulang (pakai item_id saja biar gak ganggu sistem lama)
$this->update_item_stok($stock->item_id);
}
}
// =========================
// DELETE JOURNAL
// =========================
$this->db->delete('journal_details', ['journal_id' => $id]);
$this->db->delete('journals', ['id' => $id]);
$this->db->trans_complete();
// =========================
// VALIDASI TRANSAKSI
// =========================
if (!$this->db->trans_status()) {
echo json_encode([
'status' => false,
'message' => 'Gagal hapus jurnal'
]);
return;
}
// ================= LOG ACTIVITY =================
log_activity(
'Jurnal',
'delete',
'Menghapus jurnal no_ref: ' . $journal->no_ref . ' ket: ' . $journal->keterangan,
'success'
);
echo json_encode([
'status' => true, // ✅ FIX
'message' => 'Jurnal berhasil dihapus',
'journal' => $journal->ref_type
]);
}
public function get_base_jurnal()
{
echo json_encode(
$this->db->order_by('kode','ASC')->get('base_journal')->result()
);
}
public function get_base_jurnal_detail($id)
{
echo json_encode(
$this->db->where('base_journal_id',$id)
->get('base_journal_detail')
->result()
);
}
private function update_item_stok($item_id)
{
$stok = $this->db->query("
SELECT COALESCE(SUM(qty * IF(tipe='masuk',1,-1)),0) as stok
FROM stock_logs
WHERE item_id = ?
", [$item_id])->row()->stok;
$this->db->where('id', $item_id)
->update('items', [
'stok' => $stok
]);
}
}
+155
View File
@@ -0,0 +1,155 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Kodebarang extends CI_Controller {
public function __construct()
{
parent::__construct();
if (!$this->session->userdata('logged_in')) {
redirect('auth');
}
}
public function index()
{
$data = ["active_menu" => "kode_barang"];
$this->load->view('partials/header', $data);
$this->load->view('kode_barang/layout');
$this->load->view('partials/footer');
}
// =========================
// GET DATA (DATATABLE)
// =========================
public function get_data()
{
$data = $this->db->get('kode_barang')->result();
$result = [];
$no = 1;
foreach ($data as $row) {
$result[] = [
$no++,
$row->kode_barang,
$row->nama,
$row->limit_stock,
'
<button class="btn btn-sm btn-secondary btn-editkode" data-id="'.$row->id.'">Edit</button>
<button class="btn btn-sm btn-danger btn-delete" data-id="'.$row->id.'">Delete</button>
'
];
}
echo json_encode(["data"=>$result]);
}
// =========================
// DETAIL
// =========================
public function detail($id)
{
echo json_encode(
$this->db->get_where('kode_barang',['id'=>$id])->row()
);
}
// =========================
// SAVE
// =========================
public function save()
{
$nama = $this->input->post('nama');
$kode = $this->input->post('kode');
$limit_stock = $this->input->post('limit_stock');
if(!$nama){
echo json_encode(['status'=>false,'message'=>'Nama wajib diisi']);
return;
}
$this->db->insert('kode_barang', [
'kode_barang' => $kode,
'nama' => $nama,
'limit_stock' => $limit_stock
]);
log_activity(
'kode_barang',
'create',
'Menambahkan kode barang: ' . $nama,
'success'
);
echo json_encode(['status'=>true,'message'=>'Gudang berhasil ditambahkan']);
}
// =========================
// UPDATE
// =========================
public function update()
{
$id = $this->input->post('id');
$kode = $this->input->post('kode');
$nama = $this->input->post('nama');
$limit_stock = $this->input->post('limit_stock');
if(!$nama){
echo json_encode(['status'=>false,'message'=>'Nama wajib diisi']);
return;
}
$this->db->where('id',$id)->update('kode_barang', [
'kode_barang' => $kode,
'nama' => $nama,
'limit_stock' => $limit_stock
]);
log_activity(
'kode_barang',
'update',
'Update kode barang ID: ' . $id . ' nama: ' . $nama,
'success'
);
echo json_encode(['status'=>true,'message'=>'Gudang berhasil diupdate']);
}
// =========================
// DELETE
// =========================
public function delete($id)
{
// optional: cek relasi ke items
$cek = $this->db->get_where('items',['warehouse_id'=>$id])->num_rows();
if($cek > 0){
echo json_encode([
'status'=>false,
'message'=>'Gudang tidak bisa dihapus karena masih dipakai item'
]);
return;
}
$this->db->delete('kode_barang',['id'=>$id]);
log_activity(
'kode_barang',
'delete',
'Hapus gudang ID: ' . $id,
'success'
);
echo json_encode(['status'=>true,'message'=>'Gudang berhasil dihapus']);
}
public function list()
{
echo json_encode(
$this->db->get('kode_barang')->result()
);
}
}
+88
View File
@@ -0,0 +1,88 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Labarugi extends CI_Controller {
public function __construct()
{
parent::__construct();
$this->load->model('DynamicModel', 'dm');
if (!$this->session->userdata('logged_in')) {
redirect('auth');
}
}
public function index()
{
if($this->session->userdata('role') != 'Admin'){
show_error('Akses ditolak', 403);
}
$data = ["active_menu" => "laba_rugi"];
$this->load->view('partials/header', $data);
$this->load->view('laporan/laba_rugi');
$this->load->view('partials/footer');
}
public function get_data()
{
$tanggal_dari = $this->input->get('tanggal_dari');
$tanggal_sampai = $this->input->get('tanggal_sampai');
$this->db->select('
a.id,
a.kode_akun,
a.nama_akun,
a.tipe,
COALESCE(SUM(jd.debit),0) as debit,
COALESCE(SUM(jd.kredit),0) as kredit
');
$this->db->from('accounts a');
// ================= JOIN
$this->db->join('journal_details jd','a.id = jd.account_id','LEFT');
$this->db->join('journals j','j.id = jd.journal_id','LEFT');
// ================= FILTER AKUN
$this->db->where('a.kategori','laba_rugi');
$this->db->where('a.is_active',1);
// ================= FILTER TANGGAL (AMAN)
if($tanggal_dari){
$this->db->where('(j.tanggal >= "'.$tanggal_dari.'" OR j.tanggal IS NULL)');
}
if($tanggal_sampai){
$this->db->where('(j.tanggal <= "'.$tanggal_sampai.'" OR j.tanggal IS NULL)');
}
$this->db->group_by('a.id');
$this->db->order_by('a.kode_akun','ASC');
$rows = $this->db->get()->result();
$revenue = 0;
$expense = 0;
foreach ($rows as $r) {
if ($r->tipe == 'revenue') {
$saldo = $r->kredit - $r->debit;
$revenue += $saldo;
}
if ($r->tipe == 'expense') {
$saldo = $r->debit - $r->kredit;
$expense += $saldo;
}
}
echo json_encode([
'data' => $rows,
'revenue' => $revenue,
'expense' => $expense,
'laba' => $revenue - $expense
]);
}
}
+284
View File
@@ -0,0 +1,284 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Leaverequests extends CI_Controller {
public function __construct()
{
parent::__construct();
$this->load->database();
}
// ==========================================
// PAGE
// ==========================================
public function index()
{
$data = [
"active_menu" => "leave_requests"
];
$data['employees'] = $this->db
->where('is_active',1)
->order_by('full_name','ASC')
->get('k_employees')
->result();
$this->load->view('partials/header', $data);
$this->load->view('employees/leave_requests', $data);
$this->load->view('partials/footer');
}
// ==========================================
// DATATABLE
// ==========================================
public function get_data()
{
$this->db->select('
lr.*,
e.full_name,
e.fingerprint_user_id
');
$this->db->from('k_leave_requests lr');
$this->db->join(
'k_employees e',
'e.id=lr.employee_id',
'left'
);
$this->db->order_by('lr.id','DESC');
$list = $this->db->get()->result();
$data = [];
$no = 1;
foreach($list as $r){
// =====================================
// LEAVE TYPE
// =====================================
$leave_type = '-';
if($r->leave_type == 'annual'){
$leave_type = 'Cuti Tahunan';
}
if($r->leave_type == 'sick'){
$leave_type = 'Sakit';
}
if($r->leave_type == 'permit'){
$leave_type = 'Izin';
}
if($r->leave_type == 'maternity'){
$leave_type = 'Cuti Melahirkan';
}
if($r->leave_type == 'unpaid'){
$leave_type = 'Cuti Tidak Dibayar';
}
// =====================================
// STATUS
// =====================================
if($r->approval_status == 'approved'){
$status = '
<span class="badge bg-success">
Disetujui
</span>
';
} elseif($r->approval_status == 'rejected'){
$status = '
<span class="badge bg-danger">
Ditolak
</span>
';
} else {
$status = '
<span class="badge bg-warning text-dark">
Pending
</span>
';
}
$data[] = [
$no++,
$r->fingerprint_user_id,
$r->full_name,
$leave_type,
$r->start_date,
$r->end_date,
$r->total_days . ' Hari',
$status,
'
<button class="btn btn-warning btn-sm btn-edit"
data-id="'.$r->id.'">
Edit
</button>
<button class="btn btn-danger btn-sm btn-delete"
data-id="'.$r->id.'">
Delete
</button>
'
];
}
echo json_encode([
"data" => $data
]);
}
// ==========================================
// DETAIL
// ==========================================
public function detail($id)
{
$data = $this->db
->get_where(
'k_leave_requests',
['id'=>$id]
)
->row();
echo json_encode($data);
}
// ==========================================
// SAVE
// ==========================================
public function save()
{
try{
$start_date = $this->input->post('start_date');
$end_date = $this->input->post('end_date');
$total_days = (
strtotime($end_date)
-
strtotime($start_date)
) / 86400 + 1;
$data = [
'employee_id' => $this->input->post('employee_id'),
'leave_type' => $this->input->post('leave_type'),
'start_date' => $start_date,
'end_date' => $end_date,
'total_days' => $total_days,
'reason' => $this->input->post('reason'),
'approval_status' => $this->input->post('approval_status')
];
$this->db->insert(
'k_leave_requests',
$data
);
echo json_encode([
'status' => true,
'message'=> 'Leave request berhasil ditambahkan'
]);
} catch(Exception $e){
echo json_encode([
'status' => false,
'message'=> $e->getMessage()
]);
}
}
// ==========================================
// UPDATE
// ==========================================
public function update()
{
try{
$id = $this->input->post('id');
$start_date = $this->input->post('start_date');
$end_date = $this->input->post('end_date');
$total_days = (
strtotime($end_date)
-
strtotime($start_date)
) / 86400 + 1;
$data = [
'employee_id' => $this->input->post('employee_id'),
'leave_type' => $this->input->post('leave_type'),
'start_date' => $start_date,
'end_date' => $end_date,
'total_days' => $total_days,
'reason' => $this->input->post('reason'),
'approval_status' => $this->input->post('approval_status')
];
$this->db->where('id',$id);
$this->db->update(
'k_leave_requests',
$data
);
echo json_encode([
'status' => true,
'message'=> 'Leave request berhasil diupdate'
]);
} catch(Exception $e){
echo json_encode([
'status' => false,
'message'=> $e->getMessage()
]);
}
}
// ==========================================
// DELETE
// ==========================================
public function delete($id)
{
$this->db->delete(
'k_leave_requests',
['id'=>$id]
);
echo json_encode([
'status' => true,
'message'=> 'Leave request berhasil dihapus'
]);
}
}
+145
View File
@@ -0,0 +1,145 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Lokasiasset extends CI_Controller {
public function __construct()
{
parent::__construct();
if (!$this->session->userdata('logged_in')) {
redirect('auth');
}
}
public function index()
{
$data = ["active_menu" => "lokasiasset"];
$this->load->view('partials/header', $data);
$this->load->view('lokasi_asset/layout');
$this->load->view('partials/footer');
}
// =========================
// GET DATA (DATATABLE)
// =========================
public function get_data()
{
$data = $this->db->get('lokasi_asset')->result();
$result = [];
$no = 1;
foreach ($data as $row) {
$result[] = [
$no++,
$row->nama,
'
<button class="btn btn-sm btn-secondary btn-editgudang" data-id="'.$row->id.'">Edit</button>
<button class="btn btn-sm btn-danger btn-delete" data-id="'.$row->id.'">Delete</button>
'
];
}
echo json_encode(["data"=>$result]);
}
// =========================
// DETAIL
// =========================
public function detail($id)
{
echo json_encode(
$this->db->get_where('lokasi_asset',['id'=>$id])->row()
);
}
// =========================
// SAVE
// =========================
public function save()
{
$nama = $this->input->post('nama');
if(!$nama){
echo json_encode(['status'=>false,'message'=>'Nama wajib diisi']);
return;
}
$this->db->insert('lokasi_asset', [
'nama' => $nama
]);
log_activity(
'kode_barang',
'create',
'Tambah gudang '.$nama,
'success'
);
echo json_encode(['status'=>true,'message'=>'Gudang berhasil ditambahkan']);
}
// =========================
// UPDATE
// =========================
public function update()
{
$id = $this->input->post('id');
$nama = $this->input->post('nama');
if(!$nama){
echo json_encode(['status'=>false,'message'=>'Nama wajib diisi']);
return;
}
$this->db->where('id',$id)->update('lokasi_asset', [
'nama' => $nama
]);
log_activity(
'kode_barang',
'update',
'Update gudang '.$nama,
'success'
);
echo json_encode(['status'=>true,'message'=>'Gudang berhasil diupdate']);
}
// =========================
// DELETE
// =========================
public function delete($id)
{
// optional: cek relasi ke items
$cek = $this->db->get_where('assets',['lokasi_asset_id'=>$id])->num_rows();
if($cek > 0){
echo json_encode([
'status'=>false,
'message'=>'Gudang tidak bisa dihapus karena masih dipakai item'
]);
return;
}
$this->db->delete('lokasi_asset',['id'=>$id]);
log_activity(
'kode_barang',
'delete',
'Delete gudang '.$nama,
'success'
);
echo json_encode(['status'=>true,'message'=>'Gudang berhasil dihapus']);
}
public function list()
{
echo json_encode(
$this->db->get('lokasi_asset')->result()
);
}
}
+170
View File
@@ -0,0 +1,170 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Neraca extends CI_Controller {
public function __construct()
{
parent::__construct();
$this->load->model('DynamicModel', 'dm');
if (!$this->session->userdata('logged_in')) {
redirect('auth');
}
}
public function index()
{
if($this->session->userdata('role') != 'Admin'){
show_error('Akses ditolak', 403);
}
$data = ["active_menu" => "neraca"];
$this->load->view('partials/header', $data);
$this->load->view('laporan/neraca');
$this->load->view('partials/footer');
}
// =========================
// LABA BERJALAN (PAKAI TANGGAL JOURNAL)
// =========================
private function get_laba_berjalan($tanggal_dari = null, $tanggal_sampai = null)
{
$this->db->select('
accounts.tipe,
COALESCE(SUM(jd.debit),0) as debit,
COALESCE(SUM(jd.kredit),0) as kredit
');
$this->db->from('accounts');
$this->db->join('journal_details jd','jd.account_id = accounts.id','LEFT');
$this->db->join('journals j','j.id = jd.journal_id','LEFT');
$this->db->where_in('accounts.tipe',['revenue','expense']);
// ================= FILTER TANGGAL
if($tanggal_dari){
$this->db->where('(j.tanggal >= "'.$tanggal_dari.'" OR j.tanggal IS NULL)');
}
if($tanggal_sampai){
$this->db->where('(j.tanggal <= "'.$tanggal_sampai.'" OR j.tanggal IS NULL)');
}
$rows = $this->db->get()->result();
$revenue = 0;
$expense = 0;
foreach ($rows as $r) {
if ($r->tipe == 'revenue') {
$revenue += ($r->kredit - $r->debit);
} else {
$expense += ($r->debit - $r->kredit);
}
}
return $revenue - $expense;
}
// =========================
// GET DATA NERACA
// =========================
public function get_data()
{
$tanggal_dari = $this->input->get('tanggal_dari');
$tanggal_sampai = $this->input->get('tanggal_sampai');
$this->db->select('
a.id,
a.kode_akun,
a.nama_akun,
a.tipe,
a.posisi,
a.is_kontra,
COALESCE(SUM(jd.debit),0) as debit,
COALESCE(SUM(jd.kredit),0) as kredit
');
$this->db->from('accounts a');
$this->db->join('journal_details jd','jd.account_id = a.id','LEFT');
$this->db->join('journals j','j.id = jd.journal_id','LEFT');
$this->db->where('a.kategori','neraca');
// ================= FILTER TANGGAL
if($tanggal_dari){
$this->db->where('(j.tanggal >= "'.$tanggal_dari.'" OR j.tanggal IS NULL)');
}
if($tanggal_sampai){
$this->db->where('(j.tanggal <= "'.$tanggal_sampai.'" OR j.tanggal IS NULL)');
}
$this->db->group_by('a.id');
$this->db->order_by('a.kode_akun','ASC');
$rows = $this->db->get()->result();
$activa = [];
$pasiva = [];
$total_activa = 0;
$total_pasiva = 0;
// ================= LABA BERJALAN
$laba = $this->get_laba_berjalan($tanggal_dari, $tanggal_sampai);
foreach ($rows as $r) {
// ================= SALDO NORMAL
if ($r->posisi == 'debit') {
$saldo = $r->debit - $r->kredit;
} else {
$saldo = $r->kredit - $r->debit;
}
// ================= KONTRA ACCOUNT
if ($r->is_kontra == 1) {
$saldo = -$saldo;
}
// ================= LABA BERJALAN
if ($r->kode_akun == '302') {
$saldo = $laba;
}
// ================= ACTIVA
if ($r->tipe == 'asset') {
$activa[] = [
'kode' => $r->kode_akun,
'nama' => $r->nama_akun,
'saldo' => $saldo
];
$total_activa += $saldo;
}
// ================= PASIVA
if (in_array($r->tipe, ['liability','equity'])) {
$pasiva[] = [
'kode' => $r->kode_akun,
'nama' => $r->nama_akun,
'saldo' => $saldo
];
$total_pasiva += $saldo;
}
}
echo json_encode([
'activa' => $activa,
'pasiva' => $pasiva,
'total_activa' => $total_activa,
'total_pasiva' => $total_pasiva,
'laba' => $laba,
'selisih' => $total_activa - $total_pasiva
]);
}
}
+112
View File
@@ -0,0 +1,112 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Neracasaldo extends CI_Controller {
public function __construct()
{
parent::__construct();
$this->load->model('DynamicModel', 'dm');
if (!$this->session->userdata('logged_in')) {
redirect('auth');
}
}
public function index()
{
if($this->session->userdata('role') != 'Admin'){
show_error('Akses ditolak', 403);
}
$data = ["active_menu" => "neraca_saldo"];
$this->load->view('partials/header', $data);
$this->load->view('laporan/neraca_saldo');
$this->load->view('partials/footer');
}
// =========================
// HITUNG LABA BERJALAN (REALTIME)
// =========================
private function get_laba_berjalan()
{
$this->db->select('
accounts.tipe,
COALESCE(SUM(journal_details.debit),0) as debit,
COALESCE(SUM(journal_details.kredit),0) as kredit
');
$this->db->from('accounts');
$this->db->join('journal_details','journal_details.account_id = accounts.id','LEFT');
$this->db->where_in('accounts.tipe',['revenue','expense']);
$rows = $this->db->get()->result();
$revenue = 0;
$expense = 0;
foreach ($rows as $r) {
if ($r->tipe == 'revenue') {
$revenue += ($r->kredit - $r->debit);
} else {
$expense += ($r->debit - $r->kredit);
}
}
return $revenue - $expense;
}
public function get_data()
{
$this->db->select('
accounts.kode_akun,
accounts.nama_akun,
accounts.tipe,
accounts.posisi,
COALESCE(SUM(journal_details.debit),0) as debit,
COALESCE(SUM(journal_details.kredit),0) as kredit
');
$this->db->from('accounts');
$this->db->join('journal_details','journal_details.account_id = accounts.id','LEFT');
// OPTIONAL (best practice)
$this->db->where('accounts.is_active',1);
$this->db->where('accounts.kategori','neraca');
$this->db->group_by('accounts.id');
$this->db->order_by('accounts.kode_akun','ASC');
$rows = $this->db->get()->result();
// =========================
// HITUNG LABA BERJALAN
// =========================
$laba = $this->get_laba_berjalan();
foreach ($rows as $r) {
// ================= SALDO NORMAL
if ($r->posisi == 'debit') {
$saldo = $r->debit - $r->kredit;
} else {
$saldo = $r->kredit - $r->debit;
}
// ================= OVERRIDE LABA BERJALAN
if ($r->kode_akun == '302') {
$saldo = $laba;
}
// ================= NORMALISASI KE KOLOM
if ($saldo >= 0 AND $r->posisi == 'debit') {
$r->saldo_debit = $saldo;
$r->saldo_kredit = 0;
} else {
$r->saldo_debit = 0;
$r->saldo_kredit = abs($saldo);
}
}
echo json_encode($rows);
}
}
+598
View File
@@ -0,0 +1,598 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Payroll extends CI_Controller {
public function __construct()
{
parent::__construct();
$this->load->database();
date_default_timezone_set('Asia/Jakarta');
}
// =====================================================
// INDEX
// =====================================================
public function index()
{
$data = [
'active_menu' => 'payroll'
];
$this->load->view('partials/header',$data);
$this->load->view('employees/payroll');
$this->load->view('partials/footer');
}
// =====================================================
// GET DATA
// =====================================================
public function get_data()
{
$month = $this->input->post('month');
$this->db->select("
p.*,
e.full_name,
pp.period_name,
pp.start_date,
pp.end_date
");
$this->db->from('k_payrolls p');
$this->db->join(
'k_employees e',
'e.id = p.employee_id'
);
$this->db->join(
'k_payroll_periods pp',
'pp.id = p.payroll_period_id'
);
if($month){
$this->db->where(
'DATE_FORMAT(pp.start_date,"%Y-%m")',
$month
);
}
$this->db->order_by(
'e.full_name',
'ASC'
);
$rows = $this->db->get()->result();
$data = [];
$no = 1;
foreach($rows as $row){
$pendapatan =
(
$row->allowance_amount
+
$row->bonus_amount
+
$row->overtime_amount
);
$potongan =
(
$row->deduction_amount
+
$row->bpjs_kesehatan_amount
+
$row->bpjs_ketenagakerjaan_amount
+
$row->tax_amount
+
$row->attendance_cut_amount
);
$status = '';
if($row->payment_status == 'paid'){
$status = '
<span class="badge bg-success">
Paid
</span>
';
} else {
$status = '
<span class="badge bg-warning text-dark">
Draft
</span>
';
}
$data[] = [
$no++,
$row->full_name,
'Rp '.number_format(
$row->basic_salary,
0,
',',
'.'
),
'Rp '.number_format(
$pendapatan,
0,
',',
'.'
),
'Rp '.number_format(
$potongan,
0,
',',
'.'
),
'Rp '.number_format(
$row->total_salary,
0,
',',
'.'
),
$status,
'
<button
class="btn btn-sm btn-warning btn-edit"
data-id="'.$row->id.'">
<i class="fa fa-edit"></i>
Edit
</button>
'
];
}
echo json_encode([
'data' => $data
]);
}
// =====================================================
// DETAIL
// =====================================================
public function detail($id)
{
$this->db->select("
p.*,
e.full_name
");
$this->db->from('k_payrolls p');
$this->db->join(
'k_employees e',
'e.id = p.employee_id'
);
$this->db->where(
'p.id',
$id
);
$payroll = $this->db->get()->row();
if(!$payroll){
echo json_encode([
'status' => false,
'message' => 'Payroll tidak ditemukan'
]);
return;
}
$items = $this->db
->where('payroll_id',$id)
->order_by('id','ASC')
->get('k_payroll_items')
->result();
echo json_encode([
'status' => true,
'payroll' => $payroll,
'employee' => [
'full_name' => $payroll->full_name
],
'items' => $items
]);
}
// =====================================================
// SAVE ITEMS
// =====================================================
public function save_items()
{
$payrollId = $this->input->post('payroll_id');
$items = $this->input->post('items');
$payroll = $this->db
->where('id',$payrollId)
->get('k_payrolls')
->row();
if(!$payroll){
echo json_encode([
'status' => false,
'message' => 'Payroll tidak ditemukan'
]);
return;
}
// =========================================
// DELETE OLD ITEMS
// =========================================
$this->db
->where('payroll_id',$payrollId)
->delete('k_payroll_items');
$allowance = 0;
$bonus = 0;
$overtime = 0;
$deduction = 0;
$bpjs = 0;
$tax = 0;
if($items){
foreach($items as $item){
$amount = (float)$item['amount'];
$insert = [
'payroll_id' => $payrollId,
'item_type' => $item['item_type'],
'item_name' => $item['item_name'],
'amount' => $amount
];
$this->db->insert(
'k_payroll_items',
$insert
);
switch($item['item_type']){
case 'allowance':
$allowance += $amount;
break;
case 'bonus':
$bonus += $amount;
break;
case 'overtime':
$overtime += $amount;
break;
case 'deduction':
$deduction += $amount;
break;
case 'bpjs':
$bpjs += $amount;
break;
case 'tax':
$tax += $amount;
break;
}
}
}
// =========================================
// TOTAL
// =========================================
$totalSalary =
(
$payroll->basic_salary
+
$allowance
+
$bonus
+
$overtime
)
-
(
$deduction
+
$bpjs
+
$tax
+
$payroll->attendance_cut_amount
);
// =========================================
// UPDATE PAYROLL
// =========================================
$this->db
->where('id',$payrollId)
->update(
'k_payrolls',
[
'allowance_amount' => $allowance,
'bonus_amount' => $bonus,
'overtime_amount' => $overtime,
'deduction_amount' => $deduction,
'bpjs_kesehatan_amount' => $bpjs,
'tax_amount' => $tax,
'total_salary' => $totalSalary
]
);
echo json_encode([
'status' => true,
'message' => 'Payroll berhasil disimpan'
]);
}
// =====================================================
// GENERATE PAYROLL
// =====================================================
public function generate_payroll()
{
$month = $this->input->post('month');
if(!$month){
echo json_encode([
'status' => false,
'message' => 'Bulan wajib dipilih'
]);
return;
}
$startDate = date(
'Y-m-01',
strtotime($month)
);
$endDate = date(
'Y-m-t',
strtotime($month)
);
// =========================================
// PERIOD
// =========================================
$period = $this->db
->where('start_date',$startDate)
->where('end_date',$endDate)
->get('k_payroll_periods')
->row();
if(!$period){
$this->db->insert(
'k_payroll_periods',
[
'period_name' => date(
'F Y',
strtotime($startDate)
),
'start_date' => $startDate,
'end_date' => $endDate,
'status' => 'draft',
'generated_at' => date('Y-m-d H:i:s')
]
);
$periodId = $this->db->insert_id();
} else {
$periodId = $period->id;
}
// =========================================
// EMPLOYEES
// =========================================
$employees = $this->db
->where('is_active',1)
->get('k_employees')
->result();
foreach($employees as $employee){
// =====================================
// ATTENDANCE
// =====================================
$attendance = $this->db
->select("
COUNT(*) as total_work_days,
SUM(attendance_status='present')
as total_present,
SUM(attendance_status='alpha')
as total_alpha,
SUM(overtime_hours)
as total_overtime_hours
")
->where('employee_id',$employee->id)
->where('attendance_date >=',$startDate)
->where('attendance_date <=',$endDate)
->get('k_attendances')
->row();
$dailySalary =
$employee->basic_salary / 30;
$alphaCut =
(
$attendance->total_alpha
* $dailySalary
);
$overtimeRate = 25000;
$overtimeAmount =
(
$attendance->total_overtime_hours
* $overtimeRate
);
$totalSalary =
(
$employee->basic_salary
+ $overtimeAmount
)
- $alphaCut;
$exist = $this->db
->where(
'payroll_period_id',
$periodId
)
->where(
'employee_id',
$employee->id
)
->get('k_payrolls')
->row();
$data = [
'payroll_period_id' => $periodId,
'employee_id' => $employee->id,
'basic_salary' => $employee->basic_salary,
'total_work_days' => (
$attendance->total_work_days ?? 0
),
'total_present' => (
$attendance->total_present ?? 0
),
'total_alpha' => (
$attendance->total_alpha ?? 0
),
'total_overtime_hours' => (
$attendance->total_overtime_hours ?? 0
),
'attendance_cut_amount' => $alphaCut,
'overtime_amount' => $overtimeAmount,
'total_salary' => $totalSalary
];
if($exist){
$this->db
->where('id',$exist->id)
->update(
'k_payrolls',
$data
);
} else {
$this->db->insert(
'k_payrolls',
$data
);
$payrollId = $this->db->insert_id();
// =================================
// AUTO ITEM OVERTIME
// =================================
if($overtimeAmount > 0){
$this->db->insert(
'k_payroll_items',
[
'payroll_id' => $payrollId,
'item_type' => 'overtime',
'item_name' => 'Lembur',
'amount' => $overtimeAmount
]
);
}
// =================================
// AUTO ITEM ALPHA
// =================================
if($alphaCut > 0){
$this->db->insert(
'k_payroll_items',
[
'payroll_id' => $payrollId,
'item_type' => 'deduction',
'item_name' => 'Potongan Alpha',
'amount' => $alphaCut
]
);
}
}
}
echo json_encode([
'status' => true,
'message' => 'Payroll berhasil digenerate'
]);
}
}
+142
View File
@@ -0,0 +1,142 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Pesan extends CI_Controller {
public function __construct()
{
parent::__construct();
$this->load->database();
// Cek apakah user sudah login
if (!$this->session->userdata('logged_in')) {
redirect('login');
}
}
public function index()
{
if (!check_permission('pesan', 'can_view')) {
show_error('Tidak memiliki izin untuk melihat detail.', 403);
}
$data = [
"active_menu" => "pesan"
];
$this->load->view('partials/header', $data);
$this->load->view('pesan/layout');
$this->load->view('partials/footer');
}
// =========================================================
// DATATABLE SERVERSIDE
// =========================================================
public function get_data()
{
if (!check_permission('pesan', 'can_view')) {
show_error('Tidak memiliki izin untuk melihat detail.', 403);
}
$table = 'notifikasi';
$column_order = array(null, 'created_at', 'type', 'nomor_whatsapp', 'email', 'pesan', 'status');
$column_search = array('pesan', 'nomor_whatsapp', 'email', 'status', 'type');
$order = array('created_at' => 'DESC');
$this->db->from($table);
$search_value = !empty($_POST['search']['value']) ? $_POST['search']['value'] : '';
// SEARCH
if ($search_value != '') {
$this->db->group_start();
foreach ($column_search as $i => $col_name) {
if ($i === 0) {
$this->db->like($col_name, $search_value);
} else {
$this->db->or_like($col_name, $search_value);
}
}
$this->db->group_end();
}
// ORDER
if (isset($_POST['order'])) {
$col_index = $_POST['order'][0]['column'];
$dir = $_POST['order'][0]['dir'];
if (isset($column_order[$col_index])) {
$this->db->order_by($column_order[$col_index], $dir);
}
} else {
$this->db->order_by(key($order), $order[key($order)]);
}
// PAGINATION
if ($_POST['length'] != -1) {
$this->db->limit($_POST['length'], $_POST['start']);
}
$query = $this->db->get();
$data = [];
$no = $_POST['start'];
foreach ($query->result() as $row) {
$no++;
// Badge for 'status'
$statusBadge = '';
switch ($row->status) {
case 'terkirim': $statusBadge = '<span class="badge bg-success">Terkirim</span>'; break;
case 'proses': $statusBadge = '<span class="badge bg-info">Proses</span>'; break;
case 'pending': $statusBadge = '<span class="badge bg-warning">Pending</span>'; break;
case 'dibaca': $statusBadge = '<span class="badge bg-primary">Dibaca</span>'; break;
default: $statusBadge = '<span class="badge bg-secondary">' . htmlspecialchars($row->status) . '</span>'; break;
}
// Type display
$typeDisplay = '';
switch ($row->type) {
case 'wa_official': $typeDisplay = 'WhatsApp Official'; break;
case 'wa_unofficial': $typeDisplay = 'WhatsApp Unofficial'; break;
case 'email': $typeDisplay = 'Email'; break;
default: $typeDisplay = htmlspecialchars($row->type); break;
}
$penerima = !empty($row->nomor_whatsapp) ? htmlspecialchars($row->nomor_whatsapp) : htmlspecialchars($row->email);
$data[] = [
$no,
date('d-m-Y H:i:s', strtotime($row->created_at)),
$typeDisplay,
$penerima,
'<div class="truncate-text">' . htmlspecialchars($row->pesan) . '</div>',
$statusBadge
];
}
// COUNT TOTAL RECORDS
$this->db->from($table);
if ($search_value != '') {
$this->db->group_start();
foreach ($column_search as $i => $col_name) {
if ($i === 0) {
$this->db->like($col_name, $search_value);
} else {
$this->db->or_like($col_name, $search_value);
}
}
$this->db->group_end();
}
$recordsFiltered = $this->db->count_all_results();
$output = [
"draw" => intval($_POST['draw']),
"recordsTotal" => $this->db->count_all($table),
"recordsFiltered" => $recordsFiltered,
"data" => $data
];
echo json_encode($output);
}
}
+50
View File
@@ -0,0 +1,50 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Qr extends CI_Controller {
public function __construct()
{
parent::__construct();
// Load library QR Code manual
require_once APPPATH . 'libraries/phpqrcode/qrlib.php';
// Cek apakah user sudah login
if (!$this->session->userdata('logged_in')) {
redirect('login');
}
}
// Tampilkan QR (dengan margin kecil)
public function show($text = "contoh")
{
header("Content-Type: image/png");
// Param: text, outfile, ECC, size, margin
QRcode::png($text, null, QR_ECLEVEL_H, 8, 1);
}
// Download QR Code
public function download($text = "contoh")
{
$fileName = "QR_" . time() . ".png";
$tempDir = FCPATH . "downloads/";
if (!is_dir($tempDir)) {
mkdir($tempDir, 0777, true);
}
$filePath = $tempDir . $fileName;
// Generate QR ke file (margin diperkecil)
QRcode::png($text, $filePath, QR_ECLEVEL_H, 8, 1);
// Force download
header('Content-Type: image/png');
header('Content-Disposition: attachment; filename="'.$fileName.'"');
readfile($filePath);
// Hapus setelah download
unlink($filePath);
}
}
+644
View File
@@ -0,0 +1,644 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Scheduler extends CI_Controller {
public function __construct()
{
parent::__construct();
$this->load->database();
date_default_timezone_set('Asia/Jakarta');
}
// =====================================================
// PROCESS ATTENDANCE LOG
// =====================================================
public function process_attendance()
{
echo "PROCESS ATTENDANCE START\n";
// =============================================
// GET PENDING LOG
// =============================================
$logs = $this->db
->where('process_status','pending')
->order_by('check_time','ASC')
->limit(1000)
->get('k_attendance_logs')
->result();
foreach($logs as $log){
try{
// =====================================
// GET EMPLOYEE
// =====================================
$employee = $this->db
->where(
'fingerprint_user_id',
$log->user_id
)
->where('is_active',1)
->get('k_employees')
->row();
if(!$employee){
$this->updateLogStatus(
$log->id,
'failed'
);
echo "FAILED EMPLOYEE : {$log->user_id}\n";
continue;
}
// =====================================
// UPDATE EMPLOYEE ID
// =====================================
$this->db
->where('id',$log->id)
->update(
'k_attendance_logs',
[
'employee_id' => $employee->id
]
);
$logDateTime = date(
'Y-m-d H:i:s',
strtotime($log->check_time)
);
// =====================================
// FIND SCHEDULE
// SUPPORT SHIFT MALAM
// =====================================
$this->db->from(
'k_employee_shift_schedules'
);
$this->db->where(
'employee_id',
$employee->id
);
$this->db->where(
'status',
'published'
);
// =====================================
// RANGE SHIFT
// =====================================
$this->db->where("
'{$logDateTime}' BETWEEN
DATE_SUB(start_datetime, INTERVAL 3 HOUR)
AND
DATE_ADD(end_datetime, INTERVAL 6 HOUR)
", null, false);
$this->db->order_by(
'start_datetime',
'DESC'
);
$schedule = $this->db->get()->row();
// =====================================
// SCHEDULE NOT FOUND
// =====================================
if(!$schedule){
$this->updateLogStatus(
$log->id,
'failed'
);
echo "FAILED SCHEDULE : {$logDateTime}\n";
continue;
}
// =====================================
// GET SHIFT
// =====================================
$shift = $this->db
->where('id',$schedule->shift_id)
->get('k_shifts')
->row();
if(!$shift){
$this->updateLogStatus(
$log->id,
'failed'
);
echo "FAILED SHIFT : {$schedule->shift_id}\n";
continue;
}
// =====================================
// ATTENDANCE DATE
// =====================================
$attendance_date = date(
'Y-m-d',
strtotime($schedule->start_datetime)
);
// =====================================
// GET ATTENDANCE
// =====================================
$attendance = $this->db
->where(
'employee_id',
$employee->id
)
->where(
'attendance_date',
$attendance_date
)
->get('k_attendances')
->row();
// =====================================
// CHECK LATE
// =====================================
$late_minutes = 0;
$maxCheckin = strtotime(
$schedule->start_datetime
) + (
$shift->late_tolerance_minutes * 60
);
if(
strtotime($logDateTime)
>
$maxCheckin
){
$late_minutes = floor(
(
strtotime($logDateTime)
-
strtotime($schedule->start_datetime)
) / 60
);
if($late_minutes < 0){
$late_minutes = 0;
}
}
// =====================================
// INSERT CHECKIN
// =====================================
if(!$attendance){
$this->db->insert(
'k_attendances',
[
'employee_id' => $employee->id,
'attendance_date' => $attendance_date,
'checkin_time' => $logDateTime,
'late_minutes' => $late_minutes,
'attendance_status' => (
$late_minutes > 0
? 'late'
: 'present'
),
'shift_id' => $shift->id,
'attendance_log_in_id' => $log->id,
'created_at' => date('Y-m-d H:i:s')
]
);
echo "CHECKIN : {$employee->full_name}\n";
} else {
// =================================
// SUDAH ADA CHECKOUT
// =================================
if(!empty($attendance->checkout_time)){
$this->updateLogStatus(
$log->id,
'processed'
);
echo "SKIP LOG (SUDAH CHECKOUT)\n";
continue;
}
// =================================
// CEK SELISIH JAM
// =================================
$diffMinutes = floor(
(
strtotime($logDateTime)
-
strtotime($attendance->checkin_time)
) / 60
);
// =================================
// MINIMAL 2 JAM
// BARU DIANGGAP CHECKOUT
// =================================
if($diffMinutes < 120){
$this->updateLogStatus(
$log->id,
'processed'
);
echo "SKIP DUPLICATE LOG : {$employee->full_name}\n";
continue;
}
// =================================
// WORK HOURS
// =================================
$workHours = (
strtotime($logDateTime)
-
strtotime($attendance->checkin_time)
) / 3600;
if($workHours < 0){
$workHours = 0;
}
// =================================
// EARLY LEAVE
// =================================
$early_leave_minutes = 0;
if(
strtotime($logDateTime)
<
strtotime($schedule->end_datetime)
){
$early_leave_minutes = floor(
(
strtotime($schedule->end_datetime)
-
strtotime($logDateTime)
) / 60
);
if($early_leave_minutes < 0){
$early_leave_minutes = 0;
}
}
// =================================
// OVERTIME
// =================================
$overtime_hours = 0;
if(
strtotime($logDateTime)
>
strtotime($schedule->end_datetime)
){
$overtime_hours = round(
(
strtotime($logDateTime)
-
strtotime($schedule->end_datetime)
) / 3600,
2
);
if($overtime_hours < 0){
$overtime_hours = 0;
}
}
// =================================
// UPDATE CHECKOUT
// =================================
$this->db
->where(
'id',
$attendance->id
)
->update(
'k_attendances',
[
'checkout_time' => $logDateTime,
'attendance_log_out_id' => $log->id,
'work_hours' => round($workHours,2),
'early_leave_minutes' => $early_leave_minutes,
'overtime_hours' => $overtime_hours
]
);
echo "CHECKOUT : {$employee->full_name}\n";
}
// =====================================
// UPDATE LOG STATUS
// =====================================
$this->updateLogStatus(
$log->id,
'processed'
);
echo "SUCCESS LOG ID : {$log->id}\n";
} catch(Exception $e){
$this->updateLogStatus(
$log->id,
'failed'
);
echo "ERROR : " . $e->getMessage() . "\n";
}
}
echo "PROCESS ATTENDANCE DONE\n";
}
// =====================================================
// AUTO CLOSE ATTENDANCE
// =====================================================
public function auto_close_attendance()
{
echo "AUTO CLOSE START\n";
// =============================================
// AMBIL ATTENDANCE BELUM CHECKOUT
// =============================================
$attendances = $this->db
->where('checkout_time IS NULL', null, false)
->get('k_attendances')
->result();
foreach($attendances as $attendance){
// =========================================
// GET SCHEDULE
// =========================================
$schedule = $this->db
->where(
'employee_id',
$attendance->employee_id
)
->where(
'shift_id',
$attendance->shift_id
)
->where(
'DATE(start_datetime)',
$attendance->attendance_date
)
->get('k_employee_shift_schedules')
->row();
if(!$schedule){
continue;
}
// =========================================
// AUTO CLOSE
// END SHIFT + 4 JAM
// =========================================
$autoCloseTime = date(
'Y-m-d H:i:s',
strtotime(
$schedule->end_datetime . ' +4 hours'
)
);
// =========================================
// BELUM WAKTUNYA
// =========================================
if(
strtotime(date('Y-m-d H:i:s'))
<
strtotime($autoCloseTime)
){
continue;
}
// =========================================
// WORK HOURS
// =========================================
$workHours = (
strtotime($schedule->end_datetime)
-
strtotime($attendance->checkin_time)
) / 3600;
if($workHours < 0){
$workHours = 0;
}
// =========================================
// AUTO CHECKOUT
// =========================================
$this->db
->where('id',$attendance->id)
->update(
'k_attendances',
[
'checkout_time' => $schedule->end_datetime,
'work_hours' => round($workHours,2),
'notes' => 'Auto checkout by system'
]
);
echo "AUTO CLOSE EMPLOYEE : {$attendance->employee_id}\n";
}
echo "AUTO CLOSE DONE\n";
}
// =====================================================
// GENERATE DAILY ATTENDANCE
// =====================================================
public function generate_daily_attendance()
{
echo "GENERATE DAILY START\n";
// =============================================
// GENERATE HARI KEMARIN
// =============================================
$date = date(
'Y-m-d',
strtotime('-1 day')
);
// =============================================
// GET ALL SCHEDULE
// =============================================
$schedules = $this->db
->where('schedule_date', $date)
->where('status','published')
->get('k_employee_shift_schedules')
->result();
foreach($schedules as $schedule){
// =========================================
// CHECK ATTENDANCE EXIST
// =========================================
$attendance = $this->db
->where(
'employee_id',
$schedule->employee_id
)
->where(
'attendance_date',
$date
)
->get('k_attendances')
->row();
if($attendance){
continue;
}
// =========================================
// CHECK HOLIDAY
// =========================================
$holiday = $this->db
->where('holiday_date',$date)
->get('k_holidays')
->row();
if($holiday){
$this->db->insert(
'k_attendances',
[
'employee_id' => $schedule->employee_id,
'attendance_date' => $date,
'attendance_status' => 'holiday',
'is_holiday' => 1,
'shift_id' => $schedule->shift_id,
'notes' => $holiday->holiday_name
]
);
continue;
}
// =========================================
// CHECK LEAVE
// =========================================
$leave = $this->db
->where(
'employee_id',
$schedule->employee_id
)
->where(
'approval_status',
'approved'
)
->where('start_date <=',$date)
->where('end_date >=',$date)
->get('k_leave_requests')
->row();
if($leave){
$this->db->insert(
'k_attendances',
[
'employee_id' => $schedule->employee_id,
'attendance_date' => $date,
'attendance_status' => $leave->leave_type,
'shift_id' => $schedule->shift_id,
'notes' => $leave->reason
]
);
continue;
}
// =========================================
// DEFAULT ALPHA
// =========================================
$this->db->insert(
'k_attendances',
[
'employee_id' => $schedule->employee_id,
'attendance_date' => $date,
'attendance_status' => 'alpha',
'shift_id' => $schedule->shift_id,
'notes' => 'Tidak hadir'
]
);
echo "GENERATE EMPLOYEE : {$schedule->employee_id}\n";
}
echo "GENERATE DAILY DONE\n";
}
// =====================================================
// UPDATE LOG STATUS
// =====================================================
private function updateLogStatus(
$id,
$status
){
$this->db
->where('id',$id)
->update(
'k_attendance_logs',
[
'process_status' => $status
]
);
}
}
+336
View File
@@ -0,0 +1,336 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Setting extends CI_Controller {
public function __construct()
{
parent::__construct();
// Cek apakah user sudah login
if (!$this->session->userdata('logged_in')) {
redirect('login');
}
if (!check_permission('setting', 'can_view')) {
show_error('Anda tidak memiliki izin untuk mengakses halaman ini.', 403);
}
}
// ==========================
// MAIN PAGE
// ==========================
public function index()
{
if (!check_permission('setting', 'can_view')) {
show_error('Tidak memiliki izin untuk melihat detail.', 403);
}
// Ambil config id=1
$cfg = $this->db->get_where("config", ["id" => 1])->row();
// Jika tidak ada data config
$config = $cfg ?: $this->defaultConfig();
// Ambil template pesan
$template_pesan = $this->db
->order_by("id", "ASC")
->get("template_pesan")
->result();
$data = [
"active_menu" => "pengaturan",
"config" => $config,
"template_pesan" => $template_pesan
];
$this->load->view('partials/header', $data);
$this->load->view('setting/layout', $data);
$this->load->view('partials/footer');
}
// ===============================
// DEFAULT CONFIG
// ===============================
private function defaultConfig()
{
if (!check_permission('setting', 'can_view')) {
show_error('Tidak memiliki izin untuk melihat detail.', 403);
}
return (object)[
"nama_lembaga" => "",
"pimpinan_lembaga" => "",
"card_background" => "",
"card_orientation" => "landscape",
"wa_mode" => "official",
"wa_phone" => "",
"wa_phone_id" => "",
"wa_waba_id" => "",
"wa_access_token" => "",
"wa_endpoint" => "",
"wa_api_key" => ""
];
}
// ===============================
// 1. SIMPAN SETTING UMUM
// ===============================
public function save_umum()
{
if (!check_permission('setting', 'can_view')) {
show_error('Tidak memiliki izin untuk melihat detail.', 403);
}
$update = [
"nama_lembaga" => $this->input->post("nama_lembaga"),
"pimpinan_lembaga" => $this->input->post("pimpinan_lembaga"),
"updated_at" => date("Y-m-d H:i:s")
];
$this->db->where("id", 1)->update("config", $update);
redirect("setting");
}
// ===============================
// 2. SIMPAN CARD + UPLOAD BG
// ===============================
public function save_card()
{
if (!check_permission('setting', 'can_view')) {
show_error('Tidak memiliki izin untuk melihat detail.', 403);
}
$orientation = $this->input->post("card_orientation");
$bg = null;
// Jika ada upload file
if (!empty($_FILES["card_background"]["name"])) {
$config['upload_path'] = './uploads/card/';
$config['allowed_types'] = 'jpg|jpeg|png';
$config['file_name'] = 'background_' . time();
if (!is_dir('./uploads/card/')) {
mkdir('./uploads/card/', 0777, true);
}
$this->load->library('upload', $config);
if ($this->upload->do_upload('card_background')) {
$file = $this->upload->data();
$bg = "uploads/card/" . $file['file_name'];
}
}
// Data update
$data = [
"card_orientation" => $orientation,
"updated_at" => date("Y-m-d H:i:s")
];
// Jika ada gambar baru
if ($bg !== null) {
$data["card_background"] = $bg;
}
$this->db->where("id", 1)->update("config", $data);
redirect("setting");
}
// ===============================
// 3. SIMPAN SETTING WHATSAPP
// ===============================
public function save_whatsapp()
{
if (!check_permission('setting', 'can_view')) {
show_error('Tidak memiliki izin untuk melihat detail.', 403);
}
$mode = $this->input->post("wa_mode");
$data = [
"wa_mode" => $mode,
"updated_at" => date("Y-m-d H:i:s")
];
if ($mode == "official") {
$data["wa_phone"] = $this->input->post("wa_phone");
$data["wa_phone_id"] = $this->input->post("wa_phone_id");
$data["wa_waba_id"] = $this->input->post("wa_waba_id");
$data["wa_access_token"] = $this->input->post("wa_access_token");
// Kosongkan unofficial
$data["wa_endpoint"] = "";
$data["wa_api_key"] = "";
} else {
$data["wa_endpoint"] = $this->input->post("wa_endpoint");
$data["wa_api_key"] = $this->input->post("wa_api_key");
// Kosongkan official
$data["wa_phone"] = "";
$data["wa_phone_id"] = "";
$data["wa_waba_id"] = "";
$data["wa_access_token"] = "";
}
$this->db->where("id", 1)->update("config", $data);
redirect("setting");
}
// ===============================
// 4. UPDATE TEMPLATE PESAN
// ===============================
public function update_template_pesan()
{
if (!check_permission('setting', 'can_view')) {
show_error('Tidak memiliki izin untuk melihat detail.', 403);
}
$data = [
"nama_template" => $this->input->post("nama_template"),
"isi_template" => $this->input->post("isi_template")
];
$id = $this->input->post("id");
$this->db->where("id", $id)->update("template_pesan", $data);
redirect("setting");
}
public function cek_whatsapp_status()
{
if (!check_permission('setting', 'can_view')) {
show_error('Tidak memiliki izin untuk melihat detail.', 403);
}
$config = $this->db->get_where("config", ["id" => 1])->row();
if (!$config) {
echo json_encode([
"status" => "error",
"img" => base_url("uploads/whatsapp/disconnect.png"),
"msg" => "Config tidak ditemukan"
]);
return;
}
// Fungsi bantu call API
function callCurl($url, $fields)
{
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_POSTFIELDS => $fields,
CURLOPT_HTTPHEADER => array( 'Content-Type: application/x-www-form-urlencoded' ),
]);
$response = curl_exec($curl);
curl_close($curl);
return $response;
}
// 1. CEK STATUS
$response = callCurl(
$config->wa_endpoint . "getState",
"apiKey=" . $config->wa_api_key
);
$data = json_decode($response, true);
if (!$data || !isset($data['results']['state'])) {
echo json_encode([
"status" => "error",
"img" => base_url('uploads/whatsapp/disconnect.png'),
"msg" => "Gagal membaca respon server WA",
"data" => $data,
"url_key" => $config->wa_endpoint . ' ' . $config->wa_api_key
]);
return;
}
$state = $data['results']['state'];
// ==========================================================
// 1. CONNECTED → tampilkan gambar connected
// ==========================================================
if ($state == "CONNECTED") {
echo json_encode([
"status" => "CONNECTED",
"img" => base_url('uploads/whatsapp/connect.png')
]);
return;
}
// ==========================================================
// 2. SERVICE_OFF → tampilkan disconnect + mulai service
// ==========================================================
if ($state == "SERVICE_OFF") {
// Mulai service
callCurl(
$config->wa_endpoint . "serviceStart",
"apiKey=" . $config->wa_api_key
);
echo json_encode([
"status" => "SERVICE_OFF",
"img" => base_url('uploads/whatsapp/disconnect.png')
]);
return;
}
// ==========================================================
// 3. SERVICE_SCAN → ambil QR
// ==========================================================
if ($state == "SERVICE_SCAN") {
$responseQR = callCurl(
$config->wa_endpoint . "getQR",
"apiKey=" . $config->wa_api_key
);
$qrData = json_decode($responseQR, true);
if (!isset($qrData['results']['qrString'])) {
echo json_encode([
"status" => "SERVICE_SCAN",
"img" => base_url('uploads/whatsapp/map_loader.gif')
]);
return;
}
echo json_encode([
"status" => "SERVICE_SCAN",
"img" => $qrData['results']['qrString']
]);
return;
}
// ==========================================================
// 4. STATUS LAIN → tampilkan loader
// ==========================================================
echo json_encode([
"status" => "UNKNOWN",
"img" => base_url('uploads/whatsapp/map_loader.gif')
]);
}
}
+147
View File
@@ -0,0 +1,147 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Shifts extends CI_Controller {
public function __construct()
{
parent::__construct();
$this->load->database();
}
// ==========================================
// PAGE
// ==========================================
public function index()
{
$data = [
"active_menu" => "shift"
];
$this->load->view('partials/header', $data);
$this->load->view('employees/shift');
$this->load->view('partials/footer');
}
// =====================================================
// DATATABLE
// =====================================================
public function get_data()
{
$list = $this->db
->order_by('id','DESC')
->get('k_shifts')
->result();
$data = [];
$no = 1;
foreach($list as $r){
$night = $r->is_night_shift == 1
? '<span class="badge bg-dark">Night Shift</span>'
: '<span class="badge bg-success">Normal</span>';
$data[] = [
$no++,
$r->shift_code,
$r->shift_name,
$r->checkin_time,
$r->checkout_time,
$r->late_tolerance_minutes . ' Menit',
$r->work_hours . ' Jam',
$night,
'
<button class="btn btn-warning btn-sm btn-edit"
data-id="'.$r->id.'">
Edit
</button>
<button class="btn btn-danger btn-sm btn-delete"
data-id="'.$r->id.'">
Delete
</button>
'
];
}
echo json_encode([
"data" => $data
]);
}
// =====================================================
// DETAIL
// =====================================================
public function detail($id)
{
$data = $this->db
->get_where('k_shifts',['id'=>$id])
->row();
echo json_encode($data);
}
// =====================================================
// SAVE
// =====================================================
public function save()
{
$data = [
'shift_code' => $this->input->post('shift_code'),
'shift_name' => $this->input->post('shift_name'),
'checkin_time' => $this->input->post('checkin_time'),
'checkout_time' => $this->input->post('checkout_time'),
'late_tolerance_minutes' => $this->input->post('late_tolerance_minutes'),
'work_hours' => $this->input->post('work_hours'),
'is_night_shift' => $this->input->post('is_night_shift')
];
$this->db->insert('k_shifts',$data);
echo json_encode([
'status' => true,
'message'=> 'Shift berhasil ditambahkan'
]);
}
// =====================================================
// UPDATE
// =====================================================
public function update()
{
$id = $this->input->post('id');
$data = [
'shift_code' => $this->input->post('shift_code'),
'shift_name' => $this->input->post('shift_name'),
'checkin_time' => $this->input->post('checkin_time'),
'checkout_time' => $this->input->post('checkout_time'),
'late_tolerance_minutes' => $this->input->post('late_tolerance_minutes'),
'work_hours' => $this->input->post('work_hours'),
'is_night_shift' => $this->input->post('is_night_shift')
];
$this->db->where('id',$id);
$this->db->update('k_shifts',$data);
echo json_encode([
'status' => true,
'message'=> 'Shift berhasil diupdate'
]);
}
// =====================================================
// DELETE
// =====================================================
public function delete($id)
{
$this->db->delete('k_shifts',['id'=>$id]);
echo json_encode([
'status' => true,
'message'=> 'Shift berhasil dihapus'
]);
}
}
+263
View File
@@ -0,0 +1,263 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Users extends CI_Controller {
public function __construct()
{
parent::__construct();
$this->load->model('DynamicModel', 'dm');
$this->load->library('form_validation');
if (!$this->session->userdata('logged_in')) {
redirect('auth');
}
}
public function index()
{
if($this->session->userdata('role') != 'Admin'){
show_error('Akses ditolak', 403);
}
$data = ["active_menu" => "users"];
$this->load->view('partials/header', $data);
$this->load->view('users/layout');
$this->load->view('partials/footer');
}
// =========================
// DATATABLE USERS + ROLES
// =========================
public function get_data()
{
$this->db->select('
users.id,
users.username,
users.nama,
roles.nama_role
');
$this->db->from('users');
$this->db->join('roles', 'roles.id = users.role_id', 'left');
$column_order = ['users.id', 'users.username', 'users.nama', 'roles.nama_role'];
$column_search = ['users.username', 'users.nama', 'roles.nama_role'];
$order = ['users.id' => 'DESC'];
$search_value = $this->input->post('search')['value'] ?? '';
if (!empty($search_value)) {
$this->db->group_start();
foreach ($column_search as $i => $col) {
if ($i === 0) {
$this->db->like($col, $search_value);
} else {
$this->db->or_like($col, $search_value);
}
}
$this->db->group_end();
}
if ($this->input->post('order')) {
$col_index = $this->input->post('order')[0]['column'];
$dir = $this->input->post('order')[0]['dir'];
if (isset($column_order[$col_index])) {
$this->db->order_by($column_order[$col_index], $dir);
}
} else {
$this->db->order_by(key($order), $order[key($order)]);
}
if ($this->input->post('length') != -1) {
$this->db->limit(
$this->input->post('length'),
$this->input->post('start')
);
}
$query = $this->db->get();
$data = $query->result();
$output = [
"draw" => intval($this->input->post('draw')),
"recordsTotal" => $this->db->count_all('users'),
"recordsFiltered" => $this->_count_filtered($search_value),
"data" => []
];
foreach ($data as $row) {
$action = '
<button class="btn btn-sm btn-warning btn-edit" data-id="'.$row->id.'">Edit</button>
<button class="btn btn-sm btn-danger btn-delete" data-id="'.$row->id.'">Hapus</button>
';
$output['data'][] = [
$row->id,
$row->username,
$row->nama,
$row->nama_role,
$action
];
}
echo json_encode($output);
}
private function _count_filtered($search_value)
{
$this->db->from('users');
$this->db->join('roles', 'roles.id = users.role_id', 'left');
if (!empty($search_value)) {
$this->db->group_start();
$this->db->like('users.username', $search_value);
$this->db->or_like('users.nama', $search_value);
$this->db->or_like('roles.nama_role', $search_value);
$this->db->group_end();
}
return $this->db->count_all_results();
}
// =========================
// STORE USER
// =========================
public function store()
{
$this->form_validation->set_rules('username', 'Username', 'required|is_unique[users.username]');
$this->form_validation->set_rules('nama', 'Nama', 'required');
$this->form_validation->set_rules('role_id', 'Role', 'required');
$this->form_validation->set_rules('password', 'Password', 'required|min_length[6]');
if ($this->form_validation->run() == FALSE) {
echo json_encode([
'status' => false,
'message' => validation_errors()
]);
return;
}
$data = [
'username' => $this->input->post('username'),
'nama' => $this->input->post('nama'),
'role_id' => $this->input->post('role_id'),
'password' => password_hash($this->input->post('password'), PASSWORD_DEFAULT)
];
$this->dm->setTable('users')->insert($data);
log_activity(
'users',
'create',
'Menambahkan user: ' . $data['username'],
'success'
);
echo json_encode([
'status' => true,
'message' => 'User berhasil ditambahkan'
]);
}
// =========================
// EDIT USER
// =========================
public function edit($id)
{
$user = $this->db->get_where('users', ['id' => $id])->row();
if ($user) {
echo json_encode([
'status' => true,
'data' => $user
]);
} else {
echo json_encode([
'status' => false,
'message' => 'User tidak ditemukan'
]);
}
}
// =========================
// UPDATE USER
// =========================
public function update()
{
$id = $this->input->post('id');
$this->form_validation->set_rules('username', 'Username', 'required');
$this->form_validation->set_rules('nama', 'Nama', 'required');
$this->form_validation->set_rules('role_id', 'Role', 'required');
if (!empty($this->input->post('password'))) {
$this->form_validation->set_rules('password', 'Password', 'min_length[6]');
}
if ($this->form_validation->run() == FALSE) {
echo json_encode([
'status' => false,
'message' => validation_errors()
]);
return;
}
$data = [
'username' => $this->input->post('username'),
'nama' => $this->input->post('nama'),
'role_id' => $this->input->post('role_id')
];
if (!empty($this->input->post('password'))) {
$data['password'] = password_hash($this->input->post('password'), PASSWORD_DEFAULT);
}
$this->dm->setTable('users')->update($id, $data);
log_activity(
'users',
'update',
'Update user ID: ' . $id . ' username: ' . $this->input->post('username'),
'success'
);
echo json_encode([
'status' => true,
'message' => 'User berhasil diupdate'
]);
}
// =========================
// DELETE USER
// =========================
public function delete($id)
{
$this->dm->setTable('users')->delete($id);
log_activity(
'users',
'delete',
'Hapus user ID: ' . $id,
'success'
);
echo json_encode([
'status' => true,
'message' => 'User berhasil dihapus'
]);
}
// =========================
// GET ROLES
// =========================
public function get_role()
{
$roles = $this->db->get('roles')->result();
echo json_encode($roles);
}
}
+145
View File
@@ -0,0 +1,145 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Warehouses extends CI_Controller {
public function __construct()
{
parent::__construct();
if (!$this->session->userdata('logged_in')) {
redirect('auth');
}
}
public function index()
{
$data = ["active_menu" => "warehouses"];
$this->load->view('partials/header', $data);
$this->load->view('warehouses/layout');
$this->load->view('partials/footer');
}
// =========================
// GET DATA (DATATABLE)
// =========================
public function get_data()
{
$data = $this->db->get('warehouses')->result();
$result = [];
$no = 1;
foreach ($data as $row) {
$result[] = [
$no++,
$row->nama,
'
<button class="btn btn-sm btn-secondary btn-editgudang" data-id="'.$row->id.'">Edit</button>
<button class="btn btn-sm btn-danger btn-delete" data-id="'.$row->id.'">Delete</button>
'
];
}
echo json_encode(["data"=>$result]);
}
// =========================
// DETAIL
// =========================
public function detail($id)
{
echo json_encode(
$this->db->get_where('warehouses',['id'=>$id])->row()
);
}
// =========================
// SAVE
// =========================
public function save()
{
$nama = $this->input->post('nama');
if(!$nama){
echo json_encode(['status'=>false,'message'=>'Nama wajib diisi']);
return;
}
$this->db->insert('warehouses', [
'nama' => $nama
]);
log_activity(
'kode_barang',
'create',
'Tambah gudang '.$nama,
'success'
);
echo json_encode(['status'=>true,'message'=>'Gudang berhasil ditambahkan']);
}
// =========================
// UPDATE
// =========================
public function update()
{
$id = $this->input->post('id');
$nama = $this->input->post('nama');
if(!$nama){
echo json_encode(['status'=>false,'message'=>'Nama wajib diisi']);
return;
}
$this->db->where('id',$id)->update('warehouses', [
'nama' => $nama
]);
log_activity(
'kode_barang',
'update',
'Update gudang '.$nama,
'success'
);
echo json_encode(['status'=>true,'message'=>'Gudang berhasil diupdate']);
}
// =========================
// DELETE
// =========================
public function delete($id)
{
// optional: cek relasi ke items
$cek = $this->db->get_where('items',['warehouse_id'=>$id])->num_rows();
if($cek > 0){
echo json_encode([
'status'=>false,
'message'=>'Gudang tidak bisa dihapus karena masih dipakai item'
]);
return;
}
$this->db->delete('warehouses',['id'=>$id]);
log_activity(
'kode_barang',
'delete',
'Delete gudang '.$nama,
'success'
);
echo json_encode(['status'=>true,'message'=>'Gudang berhasil dihapus']);
}
public function list()
{
echo json_encode(
$this->db->get('warehouses')->result()
);
}
}
+25
View File
@@ -0,0 +1,25 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Welcome extends CI_Controller {
/**
* Index Page for this controller.
*
* Maps to the following URL
* http://example.com/index.php/welcome
* - or -
* http://example.com/index.php/welcome/index
* - or -
* Since this controller is set as the default controller in
* config/routes.php, it's displayed at http://example.com/
*
* So any other public methods not prefixed with an underscore will
* map to /index.php/welcome/<method_name>
* @see https://codeigniter.com/userguide3/general/urls.html
*/
public function index()
{
$this->load->view('login');
}
}
+11
View File
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<title>403 Forbidden</title>
</head>
<body>
<p>Directory access is forbidden.</p>
</body>
</html>