load->model('DynamicModel', 'dm'); $this->load->library('JournalService'); $this->load->library('NumberingService'); $this->load->library('JournalWorkflowService'); 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->where('company_id',$this->companycontext->id())->count_all_results('journals'); // ================= QUERY FILTERED (HITUNG) $this->db->from('journals')->where('company_id',$this->companycontext->id()); 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, journals.status, journals.is_reversed, 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'); $this->db->where('journals.company_id',$this->companycontext->id()); 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++; $isAdmin = $this->session->userdata('role') === 'Admin'; $aksi = ' '; if ($row->status === 'draft') { $aksi .= ' '; if ($isAdmin) $aksi .= ' '; } if ($isAdmin && $row->status === 'submitted') { $aksi .= ' '; $aksi .= ' '; } if ($isAdmin && $row->status === 'posted') { $aksi .= ''; } $statusClass = array('draft'=>'secondary','submitted'=>'info','approved'=>'primary','posted'=>'success','rejected'=>'danger','reversed'=>'warning'); $badge = ''.htmlspecialchars(strtoupper($row->status), ENT_QUOTES, 'UTF-8').''; $data[] = [ $no, $row->tanggal, $row->no_ref, $row->keterangan, number_format($row->total_debit,0,',','.'), number_format($row->total_kredit,0,',','.'), $badge, $aksi ]; } echo json_encode([ "draw" => $draw, "recordsTotal" => $totalAll, "recordsFiltered" => $filtered, "data" => $data ]); } // ========================= // GET ACCOUNTS // ========================= public function get_accounts() { $data = $this->db->where(['company_id'=>$this->companycontext->id(),'is_active'=>1,'allow_posting'=>1])->order_by('kode_akun','ASC')->get('accounts')->result(); echo json_encode($data); } // ========================= // SAVE // ========================= public function save() { $tanggal = (string) $this->input->post('tanggal', true); $keterangan = trim((string) $this->input->post('keterangan', true)); $accounts = (array) $this->input->post('account_id'); $debit = (array) $this->input->post('debit'); $kredit = (array) $this->input->post('kredit'); try { $noRef = $this->numberingservice->next('journal', $tanggal); $lines = array(); foreach ($accounts as $index => $accountId) { $lineDebit = isset($debit[$index]) ? $this->parseNominal($debit[$index]) : 0; $lineCredit = isset($kredit[$index]) ? $this->parseNominal($kredit[$index]) : 0; if ($lineDebit <= 0 && $lineCredit <= 0) continue; $lines[] = array( 'account_id' => $accountId, 'debit' => $lineDebit, 'kredit' => $lineCredit ); } $journalId = $this->journalservice->create(array( 'tanggal' => $tanggal, 'no_ref' => $noRef, 'keterangan' => $keterangan, 'ref_type' => 'manual_journal', 'created_by' => (int) $this->session->userdata('user_id') ), $lines); log_activity('Jurnal', 'create', 'Membuat jurnal No Ref: ' . $noRef, 'success'); return json_response(true, 'Jurnal berhasil disimpan.', array( 'journal_id' => $journalId, 'no_ref' => $noRef )); } catch (Throwable $exception) { return business_exception_response($exception); } } /** Menerima nominal raw maupun berformat 20,000 / 20.000 / 20.000,50. */ private function parseNominal($value) { $value = trim(str_ireplace(array('Rp', ' '), '', (string) $value)); if ($value === '') return 0.0; $comma = strrpos($value, ','); $dot = strrpos($value, '.'); if ($comma !== false && $dot !== false) { $value = $comma > $dot ? str_replace(',', '.', str_replace('.', '', $value)) : str_replace(',', '', $value); } elseif ($comma !== false) { $decimal = strlen($value) - $comma - 1; $value = $decimal > 0 && $decimal <= 2 ? str_replace(',', '.', $value) : str_replace(',', '', $value); } elseif ($dot !== false && strlen($value) - $dot - 1 === 3) { $value = str_replace('.', '', $value); } return is_numeric($value) ? round((float) $value, 2) : 0.0; } // ========================= // DETAIL // ========================= public function detail($id) { $header = $this->db->get_where('journals',['id'=>$id,'company_id'=>$this->companycontext->id()])->row(); if (!$header) return json_response(false, 'Jurnal tidak ditemukan.', array(), 404); $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(); $history = $this->db ->select('journal_status_histories.*, users.nama changed_by_name') ->from('journal_status_histories') ->join('users', 'users.id = journal_status_histories.changed_by', 'left') ->where('journal_id', $id) ->order_by('journal_status_histories.id', 'ASC') ->get()->result(); echo json_encode([ 'header'=>$header, 'detail'=>$detail, 'user' =>$user, 'history'=>$history ]); } // ========================= // DELETE // ========================= public function delete($id) { if (strtoupper($this->input->method()) !== 'POST') { return json_response(false, 'Metode request tidak diizinkan.', array(), 405); } $this->requireAdmin(); $journal = $this->db->get_where('journals', ['id' => $id])->row(); // VALIDASI if (!$journal) { echo json_encode([ 'status' => false, 'message' => 'Jurnal tidak ditemukan' ]); return; } if (!in_array($journal->status, array('draft', 'rejected'), true)) { return json_response(false, 'Hanya jurnal draft atau rejected yang dapat dihapus.', array(), 422); } $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 submit($id) { return $this->workflowAction(function () use ($id) { return $this->journalworkflowservice->submit($id, $this->session->userdata('user_id')); }, 'Jurnal berhasil diajukan.'); } public function approve($id) { $this->requireAdmin(); return $this->workflowAction(function () use ($id) { return $this->journalworkflowservice->approve($id, $this->session->userdata('user_id')); }, 'Approval jurnal berhasil diproses. Setelah approval final, jurnal otomatis diposting.'); } public function post($id) { $this->requireAdmin(); return $this->workflowAction(function () use ($id) { return $this->journalworkflowservice->post($id, $this->session->userdata('user_id')); }, 'Jurnal berhasil diposting.'); } public function reject($id) { $this->requireAdmin(); $reason = $this->input->post('reason', true); return $this->workflowAction(function () use ($id, $reason) { return $this->journalworkflowservice->reject($id, $this->session->userdata('user_id'), $reason); }, 'Jurnal berhasil ditolak.'); } public function reverse($id) { $this->requireAdmin(); $date = $this->input->post('date', true); $reason = $this->input->post('reason', true); return $this->workflowAction(function () use ($id, $date, $reason) { return $this->journalworkflowservice->reverse($id, $date, $this->session->userdata('user_id'), $reason); }, 'Reversal jurnal berhasil dibuat.'); } private function workflowAction(callable $callback, $message) { if (strtoupper($this->input->method()) !== 'POST') { return json_response(false, 'Metode request tidak diizinkan.', array(), 405); } try { $result = $callback(); log_activity('Jurnal', 'workflow', $message, 'success'); return json_response(true, $message, array('result' => $result)); } catch (Throwable $exception) { return business_exception_response($exception); } } 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 ]); } }