Files
2026-07-30 09:49:00 +07:00

1577 lines
47 KiB
PHP

<?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');
}
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)
->where('status !=', 'sold_out')
->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 = '<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>';
$btnInsertSn = '<a href="'.base_url('items/item_barcodes/'.$row->id).'" class="btn btn-secondary btn-sm m-1">Barcode</a>';
$btnPrintBarcode = '<button class="btn btn-secondary btn-sm btn-print m-1" data-id="'.htmlspecialchars($row->id, ENT_QUOTES, 'UTF-8').'"><i class="bi bi-printer"></i> Print Barcode</button>';
$btnPost = '<button class="btn btn-success btn-sm btn-updatePosting m-1" data-id="'.htmlspecialchars($row->id, ENT_QUOTES, 'UTF-8').'">Posting</button>';
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 = '<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 . $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('item_movements',['item_id'=>$id]);
$this->db->delete('item_barcodes',['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.barcode,
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
]);
$this->db->where('barcode', $barcode->barcode)->where('status', 'active')->update('item_technician', ['status' => 'inactive', 'deleted_at' => date('Y-m-d H:i:s')]);
// 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.'
]);
}
}