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'); } public function item_barcodes($item_id) { $data = [ "active_menu" => "draft_items", "item_id" => $item_id ]; $this->load->view('partials/header', $data); $this->load->view('items/barcode_sn', $data); $this->load->view('partials/footer'); } public function item_barcodes_history() { $data = [ "active_menu" => "item_historys" ]; $this->load->view('partials/header', $data); $this->load->view('items/item_historys', $data); $this->load->view('partials/footer'); } public function scan_history() { $barcode = trim($this->input->post('barcode')); if(empty($barcode)){ echo json_encode([ 'status'=>false, 'message'=>'QR ID tidak boleh kosong' ]); return; } $item = $this->db ->select(" item_barcodes.id, item_barcodes.barcode, item_barcodes.serial_number, item_barcodes.status, item_barcodes.qty_awal, item_barcodes.qty_sisa, items.nama_barang, items.kode_detail ") ->from('item_barcodes') ->join('items','items.id=item_barcodes.item_id') ->where('item_barcodes.barcode',$barcode) ->or_where('item_barcodes.serial_number',$barcode) ->get() ->row(); if(!$item){ echo json_encode([ 'status'=>false, 'message'=>'Barcode tidak ditemukan.' ]); return; } $history = $this->db ->select(" item_movements.*, w1.nama as from_warehouse, w2.nama as to_warehouse ") ->from('item_movements') ->join( 'warehouses w1', 'w1.id=item_movements.from_id AND item_movements.from_type="warehouse"', 'left' ) ->join( 'warehouses w2', 'w2.id=item_movements.to_id AND item_movements.to_type="warehouse"', 'left' ) ->where('barcode_id',$item->id) ->order_by('created_at','DESC') ->get() ->result(); echo json_encode([ 'status'=>true, 'item'=>$item, 'history'=>$history ]); } public function load_data_barcode($item_id) { $item = $this->db ->where('id', $item_id) ->get('items') ->row(); if (!$item) { echo json_encode([ 'status' => false, 'message' => 'Data barang tidak ditemukan.' ]); return; } $next = $this->db ->where('item_id', $item_id) ->where('serial_number IS NULL', NULL, false) ->order_by('id', 'ASC') ->get('item_barcodes') ->row(); $list = $this->db ->where('item_id', $item_id) ->order_by('id', 'ASC') ->get('item_barcodes') ->result(); $total = count($list); $done = 0; foreach ($list as $row) { if (!empty($row->serial_number)) { $done++; } } echo json_encode([ 'status' => true, 'item_status' => $item->status, 'next' => $next, 'list' => $list, 'total' => $total, 'done' => $done, 'percent' => $total > 0 ? round(($done / $total) * 100) : 0 ]); } public function print_barcode($item_id) { require_once APPPATH.'third_party/fpdf/fpdf.php'; require_once APPPATH.'libraries/phpqrcode/qrlib.php'; $barcodes = $this->db ->where('item_id',$item_id) ->order_by('id','ASC') ->get('item_barcodes') ->result(); if(empty($barcodes)){ show_error('Barcode tidak ditemukan.'); } $column=(int)$this->input->get('column'); if(!in_array($column,[1,2,3])){ $column=3; } $duplicate=(int)$this->input->get('duplicat'); if($duplicate<1){ $duplicate=1; } $labelWidth=33.3; $labelHeight=23.2; $spaceX=0; $spaceY=0; $rowPerPage=6; $paperWidth=$labelWidth*$column; $paperHeight=$labelHeight*$rowPerPage; $marginLeft=0; $marginTop=0; $labelPerPage=$column*$rowPerPage; $pdf=new FPDF('P','mm',array($paperWidth,$paperHeight)); $pdf->SetMargins(0,0,0); $pdf->SetAutoPageBreak(false); $pdf->AddPage(); $tempDir=FCPATH.'uploads/temp_qr/'; if(!is_dir($tempDir)){ mkdir($tempDir,0777,true); } $printData=[]; foreach($barcodes as $row){ for($i=0;$i<$duplicate;$i++){ $printData[]=$row; } } foreach($printData as $i=>$row){ if($i>0 && $i%$labelPerPage==0){ $pdf->AddPage(); } $pageIndex=$i%$labelPerPage; $col=$pageIndex%$column; $line=floor($pageIndex/$column); // $x=$marginLeft+(($labelWidth+$spaceX)*$col); // $y=$marginTop+(($labelHeight+$spaceY)*$line); $x=$col*$labelWidth; $y=$line*$labelHeight; $qrFile=$tempDir.$row->barcode.'.png'; if(!file_exists($qrFile)){ QRcode::png( $row->barcode, $qrFile, QR_ECLEVEL_L, 4, 1 ); } $qrSize=15; $pdf->Image( $qrFile, $x+16, $y+4, $qrSize, $qrSize ); $textWidth=14; $pdf->SetFont('Arial','B',7); $pdf->SetXY($x+2,$y+4); $pdf->MultiCell( $textWidth, 3, $row->barcode, 0, 'L' ); $serial=empty($row->serial_number)?'-':$row->serial_number; $pdf->SetFont('Arial','B',4.5); $pdf->SetXY($x+2,$y+11); $pdf->MultiCell( $textWidth+2, 3, 'SN: ' . $serial, 0, 'L' ); } $pdf->Output( 'I', 'Barcode-'.$item_id.'.pdf' ); } public function save_serial_number() { $barcode_id = $this->input->post('barcode_id'); $serial = trim($this->input->post('serial_number')); if($serial==""){ echo json_encode(array( 'status'=>false, 'message'=>'Serial Number wajib diisi' )); return; } $cek = $this->db ->where('serial_number',$serial) ->count_all_results('item_barcodes'); if($cek){ echo json_encode(array( 'status'=>false, 'message'=>'Serial Number sudah digunakan' )); return; } $barcode = $this->db ->where('id',$barcode_id) ->get('item_barcodes') ->row(); $this->db->where('id',$barcode_id); $this->db->update('item_barcodes',array( 'serial_number'=>$serial )); // langsung kirim data terbaru $_GET['item_id']=$barcode->item_id; $this->load_data_barcode($barcode->item_id); } public function delete_serial_number() { $barcode_id = (int)$this->input->post('barcode_id'); if ($barcode_id <= 0) { echo json_encode([ 'status' => false, 'message' => 'Barcode tidak valid.' ]); return; } // Ambil data barcode $barcode = $this->db ->where('id', $barcode_id) ->get('item_barcodes') ->row(); if (!$barcode) { echo json_encode([ 'status' => false, 'message' => 'Barcode tidak ditemukan.' ]); return; } // Hapus Serial Number $this->db ->where('id', $barcode_id) ->update('item_barcodes', [ 'serial_number' => NULL ]); // Kirim ulang data terbaru $this->load_data_barcode($barcode->item_id); } public function get_barcode_item() { $barcode = trim($this->input->post('barcode')); if ($barcode == '') { echo json_encode([ 'status' => false, 'message' => 'Barcode kosong.' ]); return; } $row = $this->db ->select(' ib.id AS barcode_id, ib.barcode, ib.serial_number, i.id AS item_id, i.kode_detail, i.nama_barang, i.status, i.harga_beli, i.harga_jual ') ->from('item_barcodes ib') ->join('items i', 'i.id = ib.item_id') ->where('ib.qty_sisa !=', 0) ->where('i.status', 'active') ->group_start() ->where('ib.barcode', $barcode) ->or_like('i.nama_barang', $barcode, 'both') ->or_like('ib.serial_number', $barcode, 'both') ->group_end() ->limit(15) ->get() ->result(); if(!$row){ echo json_encode([ 'status'=>false, 'message'=>'Barcode tidak ditemukan.', 'barcode' => $barcode ]); return; } echo json_encode([ 'status'=>true, 'data'=>$row ]); } public function get_list_barcode_id($item_id) { // $item_id = trim($this->input->post('item_id')); if ($item_id == '') { echo json_encode([ 'status' => false, 'message' => 'Item belum dipilih' ]); return; } $row = $this->db ->select(' ib.id AS barcode_id, ib.barcode, ib.serial_number, ib.qty_awal, ib.qty_sisa, i.id AS item_id, i.kode_detail, i.nama_barang, i.status, i.harga_beli, i.harga_jual ') ->from('item_barcodes ib') ->join('items i','i.id=ib.item_id') ->where('ib.item_id',$item_id) ->where('ib.qty_sisa !=', 0) ->where_in('ib.status', ['installed', 'reserved', 'available']) ->get() ->result(); if(!$row){ echo json_encode([ 'status'=>false, 'message'=>'Item barang tidak ditemukan.' ]); return; } echo json_encode($row); } // ========================= // 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, kode.need_serial_number, ( 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 '); $this->db->join('kode_barang kode', 'items.kode_id = kode.id', 'left'); 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(); $result = []; $no = 1; foreach($data as $row){ $btnDetail = ''; $btnEdit = ''; $btnDelete = ''; $btnInsertSn = 'Barcode'; $btnPrintBarcode = ''; $btnPost = ''; if ($row->need_serial_number == 'true') { $list = $this->db ->where('item_id', $row->id) ->order_by('id', 'ASC') ->get('item_barcodes') ->result(); $total = count($list); $done = 0; foreach ($list as $barcode) { if (!empty($barcode->serial_number)) { $done++; } } $total_sn = $total; $done_sn = $done; if ($row->status == 'draft') { if ($total_sn == $done_sn) { $btnSN = $btnInsertSn . $btnPrintBarcode; $btnPostview = $btnPost; } else { $btnSN = $btnInsertSn ; $btnPostview = ''; } } else { $btnSN = $btnPrintBarcode; } } else { $btnSN = $btnPrintBarcode; $btnPostview = $btnPost; } if($row->status == 'draft'){ $action = $btnDetail . $btnPostview . $btnDelete . $btnSN; } else { if($this->session->userdata('role') == 'Admin') { $btnAdjust = ''; } else { $btnAdjust = ''; } $action = $btnDetail . $btnEdit . $btnAdjust . $btnSN; } $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'); $tracking_qty = $this->input->post('tracking_qty'); $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', 'account_id' => $account_kas ]); 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; if ($kode->tracking_type != 'UNIT') { if (empty($tracking_qty) || $tracking_qty <= 0) { echo json_encode(array( 'status' => false, 'message' => 'Jumlah Per (Roll, Box, Dus) harus diisi' )); return; } if ($qty % $tracking_qty != 0) { echo json_encode(array( 'status' => false, 'message' => 'Jumlah Barang harus habis dibagi Jumlah Per (Roll, Box, Dus)' )); return; } } if ($kode->tracking_type == 'UNIT') { $unit_qty = $qty; $tracking_qty = 1; } else { $unit_qty = $qty / $tracking_qty; } $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') ]); // ================= Item Barcode Masuk ================= for ($i = 1; $i <= $unit_qty; $i++) { // $barcode = sprintf('%s-%04d', $kode_detail, $i); $barcode = strtoupper($kode_detail) . '-' . strtoupper(bin2hex(random_bytes(2))); $this->db->insert('item_barcodes',[ 'item_id' => $item_id, 'barcode' => $barcode, 'serial_number' => null, 'warehouse_id' => $warehouse_id, 'qty_awal' => $tracking_qty, 'qty_sisa' => $tracking_qty, 'created_at' => date('Y-m-d H:i:s') ]); $barcode_id = $this->db->insert_id(); $this->db->insert('item_movements',[ 'item_id' => $item_id, 'barcode_id' => $barcode_id, 'qty' => $tracking_qty, 'from_type' => 'supplier', //enum('supplier','warehouse','technician','customer') 'to_type' => 'warehouse', //enum('supplier','warehouse','technician','customer') 'movement_type' => 'purchase', //enum('purchase','transfer','installation','uninstallation','supplier_return','customer_return','adjustment') 'notes' => 'Pembelian barang dari supplier', '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) foreach ($account_kas as $row) { $this->db->insert('journal_details', [ 'journal_id' => $jid, 'account_id' => $row['account_id'], 'debit' => 0, 'kredit' => str_replace('.', '', $row['nominal']) ]); } $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'); $keterangan_keluar = $this->input->post('keterangan_keluar'); // mode baru: single barcode terpilih $selected_barcode = (int)$this->input->post('selected_barcode'); if ($selected_barcode > 0) { if (!$account_biaya || !$tanggal_keluar) { echo json_encode(['status'=>false,'message'=>'Data tidak lengkap']); return; } $barcode = $this->db ->select(' ib.id as barcode_id, ib.item_id, ib.qty_sisa, ib.warehouse_id, ib.status as barcode_status, i.nama_barang, i.harga_beli ') ->from('item_barcodes ib') ->join('items i', 'i.id = ib.item_id') ->where('ib.id', $selected_barcode) ->get() ->row(); if (!$barcode) { echo json_encode(['status'=>false,'message'=>'Barcode tidak ditemukan']); return; } if ((float)$barcode->qty_sisa <= 0) { echo json_encode(['status'=>false,'message'=>'Qty barcode sudah habis']); return; } if (empty($barcode->warehouse_id)) { echo json_encode(['status'=>false,'message'=>'Warehouse barcode tidak valid']); return; } $qty_keluar = 1; $nilai = $qty_keluar * (float)$barcode->harga_beli; $this->db->trans_start(); // stock log keluar per item $this->db->insert('stock_logs',[ 'item_id' => $barcode->item_id, 'warehouse_id' => $barcode->warehouse_id, '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').'-'.$barcode->item_id, 'keterangan' => 'Pengeluaran Barang '.$barcode->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(); $this->db->insert('journal_details',[ 'journal_id'=>$jid, 'account_id'=>$account_biaya, 'debit'=>$nilai, 'kredit'=>0 ]); $this->db->insert('journal_details',[ 'journal_id'=>$jid, 'account_id'=>21, 'debit'=>0, 'kredit'=>$nilai ]); // START - Item Movement & item barcodes update status $this->db->where('barcode_id', $barcode->barcode_id); $this->db->order_by('created_at', 'DESC'); $this->db->limit(1); $item_move = $this->db->get('item_movements')->row(); if($barcode->qty_sisa < $qty_keluar) { echo json_encode(['status'=>false, 'message'=>'Stok tidak cukup. Sisa: '.$barcode->qty_sisa]); $this->db->trans_rollback(); return; } $this->db->insert('item_movements',[ 'item_id' => $barcode->item_id, 'barcode_id' => $barcode->barcode_id, 'qty' => $qty_keluar, 'from_type' => $item_move ? $item_move->to_type : 'warehouse', //enum('supplier','warehouse','technician','customer','invoice','asset') 'to_type' => 'technician', //enum('supplier','warehouse','technician','customer','invoice','asset') 'movement_type' => 'installation', //enum('purchase','transfer','installation','uninstallation','supplier_return','customer_return','adjustment','sold_out','asset') 'notes' => $keterangan_keluar, 'created_at' => date('Y-m-d H:i:s') ]); // update qty_sisa barcode $new_qty_sisa = (float)$barcode->qty_sisa - $qty_keluar; $this->db->where('id', $barcode->barcode_id)->update('item_barcodes', [ 'qty_sisa' => $new_qty_sisa, 'status' => $new_qty_sisa <= 0 ? 'installed' : $barcode->barcode_status ]); // END - Item Movement & item barcodes update status $this->db->trans_complete(); if (!$this->db->trans_status()) { echo json_encode(['status'=>false,'message'=>'Gagal proses']); return; } $this->update_item_stok($barcode->item_id); log_activity( 'items', 'stock_out', 'Keluar barcode: '.$barcode->barcode_id.' | '.$barcode->nama_barang.' qty: '.$qty_keluar.' ( '.$keterangan_keluar.' )', 'success' ); echo json_encode(['status'=>true,'message'=>'Barang berhasil dikeluarkan']); return; } // fallback flow lama $warehouse_id_keluar = $this->input->post('warehouse_id_keluar'); $barang_id = $this->input->post('barang_id'); $qty_keluar = (int)$this->input->post('qty_keluar'); if(!$barang_id || !$warehouse_id_keluar || $qty_keluar <= 0){ echo json_encode(['status'=>false,'message'=>'Data tidak valid']); return; } $item = $this->db->get_where('items',['id'=>$barang_id])->row(); if(!$item){ echo json_encode(['status'=>false,'message'=>'Item tidak ditemukan']); return; } $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; $this->db->trans_start(); $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(); $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(); $this->db->insert('journal_details',[ 'journal_id'=>$jid, 'account_id'=>$account_biaya, 'debit'=>$nilai, 'kredit'=>0 ]); $this->db->insert('journal_details',[ 'journal_id'=>$jid, 'account_id'=>21, 'debit'=>0, 'kredit'=>$nilai ]); $this->db->trans_complete(); 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); } public function update_barcode_item() { $items = $this->db ->select(" i.id, i.nama_barang, i.stok, i.kode_detail, kb.tracking_type, kb.kode_barang ") ->from('items i') ->join('kode_barang kb', 'kb.id=i.kode_id') ->where('i.status', 'active') ->get() ->result(); $this->db->trans_begin(); foreach ($items as $item) { // =========================================== // Qty per barcode // =========================================== if ($item->tracking_type == 'UNIT') { $qty_per_barcode = 1; } else { $qty_per_barcode = ($item->kode_barang == 'FO1') ? 1000 : 2000; } // =========================================== // Jumlah barcode yang seharusnya // =========================================== $target = (int) ceil($item->stok / $qty_per_barcode); if ($target <= 0) { continue; } // =========================================== // Jumlah barcode yang sudah ada // =========================================== $barcode_exist = $this->db ->where('item_id', $item->id) ->count_all_results('item_barcodes'); if ($barcode_exist >= $target) { continue; } // =========================================== // Total qty barcode yang sudah ada // =========================================== $qty_existing = $this->db ->select_sum('qty_awal') ->where('item_id', $item->id) ->get('item_barcodes') ->row(); $qty_existing = (float) ($qty_existing->qty_awal ?? 0); // Sisa stok yang belum memiliki barcode $qty_remaining = $item->stok - $qty_existing; if ($qty_remaining <= 0) { continue; } $need = $target - $barcode_exist; for ($i = 1; $i <= $need; $i++) { // Barcode terakhir akan berisi sisa stok $qty = min($qty_per_barcode, $qty_remaining); $barcode = strtoupper($item->kode_detail) . '-' . strtoupper(bin2hex(random_bytes(2))); // ============================= // Insert Barcode // ============================= $this->db->insert('item_barcodes', [ 'item_id' => $item->id, 'barcode' => $barcode, 'warehouse_id' => null, 'qty_awal' => $qty, 'qty_sisa' => $qty, 'status' => 'available' ]); $barcode_id = $this->db->insert_id(); // ============================= // Insert Movement // ============================= $this->db->insert('item_movements', [ 'item_id' => $item->id, 'barcode_id' => $barcode_id, 'qty' => $qty, 'from_type' => 'supplier', 'from_id' => null, 'to_type' => 'warehouse', 'to_id' => null, 'movement_type' => 'purchase', 'reference_type' => 'system_generate', 'reference_id' => null, 'notes' => 'Generate barcode otomatis' ]); $qty_remaining -= $qty; if ($qty_remaining <= 0) { break; } } } if ($this->db->trans_status() === FALSE) { $this->db->trans_rollback(); echo json_encode([ 'status' => false, 'message' => 'Gagal generate barcode.' ]); return; } $this->db->trans_commit(); echo json_encode([ 'status' => true, 'message' => 'Barcode berhasil diperbarui.' ]); } }