From 3113d772426ee1d10fd52492826663d86b94926b Mon Sep 17 00:00:00 2001 From: Wian Drs Date: Sun, 12 Jul 2026 22:10:02 +0700 Subject: [PATCH] Perubahan barcode dan inventory --- .gitignore | 5 + TODO.md | 19 + application/config/routes.php | 4 + application/controllers/Asset.php | 153 +++- application/controllers/Cronjob.php | 324 ++++++++- application/controllers/Customers.php | 2 + application/controllers/Employees.php | 26 + .../controllers/Generateneracasaldo.php | 209 +++--- application/controllers/Invoices.php | 259 ++++--- application/controllers/Items.php | 620 +++++++++++----- application/controllers/Kodebarang.php | 121 +++- application/controllers/Neracasaldo.php | 103 +-- application/libraries/PDF_Neracasaldo.php | 100 +-- application/views/asset/layout.php | 346 ++++++++- application/views/invoices/detail.php | 165 +++-- application/views/items/draft_items.php | 661 +++++++++--------- application/views/items/item_historys.php | 406 +++++++++++ application/views/items/layout.php | 429 ++++++++---- application/views/kode_barang/layout.php | 44 +- application/views/laporan/neraca_saldo.php | 119 ++-- application/views/partials/header.php | 12 +- 21 files changed, 2989 insertions(+), 1138 deletions(-) create mode 100644 TODO.md create mode 100644 application/views/items/item_historys.php diff --git a/.gitignore b/.gitignore index ec539bb..25556a5 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,11 @@ application/config/config.php application/config/database.php +application/controllers/Dbcompare.php .htaccess +uploads/temp_qr/*.png + application/cache/* !application/cache/index.html @@ -32,3 +35,5 @@ user_guide_src/cilexer/pycilexer.egg-info/* *.sublime-project /tests/tests/ /tests/results/ + + diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..b70cf2c --- /dev/null +++ b/TODO.md @@ -0,0 +1,19 @@ +# TODO - Update Cronjob depreciation_asset() + +- [x] Review dan lock aturan tanggal penyusutan bulanan berdasarkan tanggal_perolehan +- [x] Update logic due date bulanan per aset (min(day_perolehan, last_day_of_month)) +- [x] Pastikan penyusutan mulai bulan berikutnya setelah perolehan +- [x] Batasi eksekusi hanya saat runDate == dueDateThisMonth +- [x] Pertahankan idempotency 1x per bulan +- [x] Samakan tanggal pencatatan mutation dan journal ke dueDateThisMonth +- [x] Verifikasi struktur response details (success/skip reason) tetap informatif + +## TODO Baru - Catch-up penyusutan multi-bulan + +- [ ] Refactor logic depreciation_asset agar memproses multi-bulan (catch-up) +- [ ] Terapkan loop per bulan dari bulan setelah tanggal patokan sampai bulan berjalan +- [ ] Pertahankan idempotent check per bulan berdasarkan asset_mutations +- [ ] Generate mutasi + jurnal per bulan yang eligible +- [ ] Update nilai buku & akumulasi penyusutan asset setelah proses loop +- [ ] Tambahkan detail response per bulan (processed/skipped reason) +- [ ] Validasi syntax PHP untuk Cronjob.php diff --git a/application/config/routes.php b/application/config/routes.php index 4ebf3e8..57494af 100644 --- a/application/config/routes.php +++ b/application/config/routes.php @@ -56,3 +56,7 @@ $route['translate_uri_dashes'] = FALSE; // untuk webhook absensi $route['iclock/cdata'] = 'AttendanceWebhook/cdata'; +// db compare structure +$route['dbcompare/sql'] = 'Dbcompare/sql'; +$route['dbcompare/sql_download'] = 'Dbcompare/sql_download'; + diff --git a/application/controllers/Asset.php b/application/controllers/Asset.php index c01e41e..e731e0f 100644 --- a/application/controllers/Asset.php +++ b/application/controllers/Asset.php @@ -6,6 +6,10 @@ class Asset extends CI_Controller { public function __construct(){ parent::__construct(); $this->load->database(); + + if (!$this->session->userdata('logged_in')) { + redirect('auth'); + } } public function index(){ @@ -21,7 +25,7 @@ class Asset extends CI_Controller { ->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') + ->order_by('assets.tanggal_perolehan','DESC') ->get() ->result(); @@ -35,6 +39,7 @@ class Asset extends CI_Controller { $d->kode_asset ?? '-', $d->nama_asset, $d->nama_lokasi ?? '-', + $d->keterangan ?? '-', number_format($d->nilai_perolehan), $d->masa_manfaat . ' bln', number_format($d->penyusutan_per_bulan), @@ -101,11 +106,12 @@ class Asset extends CI_Controller { 'nilai_residu' => $residu, 'penyusutan_per_bulan' => $penyusutan, 'nilai_buku' => $nilai_perolehan, - 'sumber' => 'manual', + 'sumber' => 'pembelian', 'account_debit_id' => $account_debit_id, 'account_kredit_id' => $account_kredit_id, 'lokasi_asset_id' => $lokasi_asset_id, - 'keterangan' => $keterangan + 'keterangan' => $keterangan, + 'qty' => 1 ]; $this->db->insert('assets',$data); @@ -142,11 +148,18 @@ class Asset extends CI_Controller { 'asset_id' => $asset_id, 'tipe' => 'perolehan', 'nilai' => $nilai_perolehan, - 'keterangan' => 'Perolehan manual: '.$keterangan + 'keterangan' => 'Perolehan manual: '.$keterangan, + 'tanggal_dibuat' => $tanggal ]); $this->db->trans_complete(); - echo json_encode(['status'=>true]); + + if ($this->db->trans_status()) { + log_activity('Asset', 'create', 'Menambah asset manual: '.$nama_asset.' (ID: '.$asset_id.')'); + echo json_encode(['status'=>true]); + } else { + echo json_encode(['status'=>false]); + } } // ================= SAVE FROM STOCK ================= @@ -157,6 +170,12 @@ class Asset extends CI_Controller { $masa = (int)$this->input->post('s_masa'); $residu = preg_replace('/\D/','',$this->input->post('s_residu')); $tanggal = $this->input->post('s_tanggal'); + $keterangan = $this->input->post('s_keterangan'); + $barcode_id = $this->input->post('barcode_id'); + $qty = (int)$this->input->post('qty'); + if($qty < 1){ + $qty = 1; + } $item = $this->db->get_where('items',['id'=>$item_id])->row(); @@ -165,15 +184,20 @@ class Asset extends CI_Controller { return; } - if($item->stok < 1){ + if(!$barcode_id){ + echo json_encode(['status'=>false,'message'=>'Barcode wajib dipilih']); + return; + } + + if($item->stok < $qty){ 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); + $nilai_perolehan = $harga * $qty; + $penyusutan = ($nilai_perolehan - $residu) / ($masa ?: 1); $this->db->trans_start(); @@ -192,10 +216,43 @@ class Asset extends CI_Controller { 'sumber' => 'gudang', 'account_debit_id' => $account_debit_id, 'account_kredit_id' => 21, // Persediaan - 'lokasi_asset_id' => $lokasi_asset_id + 'lokasi_asset_id' => $lokasi_asset_id, + 'keterangan' => $keterangan, + 'qty' => $qty ]); $asset_id = $this->db->insert_id(); + + // START - Item Movement & item barcodes update status + $this->db->where('item_id', $item_id); + $this->db->order_by('created_at', 'DESC'); + $this->db->limit(1); + $item_move = $this->db->get('item_movements')->row(); + + // get item_barcode + $itemBar = $this->db->get_where('item_barcodes', ['id'=>$barcode_id])->row(); + + if($itemBar->qty_sisa < $qty) { + echo json_encode(['status'=>false, 'message'=>'Stok tidak cukup. Sisa: '.$itemBar->qty_sisa]); + $this->db->trans_rollback(); + return; + } + + $total_sisa = $itemBar->qty_sisa - $qty; + $status_bar = ($total_sisa == 0) ? 'installed' : 'available'; + + $this->db->insert('item_movements',[ + 'item_id' => $item_id, + 'barcode_id' => $barcode_id, + 'qty' => $qty, + 'from_type' => $item_move ? $item_move->to_type : 'warehouse', //enum('supplier','warehouse','technician','customer','invoice','asset') + 'to_type' => 'asset', //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, + 'created_at' => date('Y-m-d H:i:s') + ]); + // Status yang boleh masuk : enum('available','reserved','installed','damaged','lost','returned_supplier','sold_out','asset') + $this->db->where('id', $barcode_id)->update('item_barcodes', ['status' => $status_bar, 'qty_sisa' => $total_sisa]); // Journal $this->db->insert('journals',[ @@ -225,9 +282,9 @@ class Asset extends CI_Controller { 'kredit' => $nilai_perolehan ]); - // UPDATE STOK ITEM (kurangi 1) + // UPDATE STOK ITEM (kurangi sesuai qty) $this->db->where('id', $item_id) - ->set('stok', 'stok-1', FALSE) + ->set('stok', 'stok-'.$qty, FALSE) ->update('items'); // MUTATION DARI GUDANG @@ -235,12 +292,18 @@ class Asset extends CI_Controller { 'asset_id' => $asset_id, 'tipe' => 'perolehan', 'nilai' => $nilai_perolehan, - 'keterangan' => 'Perolehan dari gudang: '.$nama_asset + 'keterangan' => 'Perolehan dari gudang: '.$nama_asset, + 'tanggal_dibuat' => $tanggal ]); $this->db->trans_complete(); - echo json_encode(['status'=>true]); + if ($this->db->trans_status()) { + log_activity('Asset', 'create', 'Menambah asset dari gudang: '.$nama_asset.' (ID: '.$asset_id.', Qty: '.$qty.')'); + echo json_encode(['status'=>true]); + } else { + echo json_encode(['status'=>false]); + } } // ================= UPDATE ================= @@ -270,7 +333,13 @@ class Asset extends CI_Controller { ]); $this->db->trans_complete(); - echo json_encode(['status'=>true]); + + if ($this->db->trans_status()) { + log_activity('Asset', 'update', 'Update asset ID: '.$id.' Nama: '.$nama_asset); + echo json_encode(['status'=>true]); + } else { + echo json_encode(['status'=>false]); + } } // ================= DELETE ================= @@ -302,7 +371,13 @@ class Asset extends CI_Controller { ]); $this->db->trans_complete(); - echo json_encode(['status'=>true]); + + if ($this->db->trans_status()) { + log_activity('Asset', 'delete', 'Hapus asset ID: '.$id.' Alasan: '.$alasan); + echo json_encode(['status'=>true]); + } else { + echo json_encode(['status'=>false]); + } } // ================= GET DATA SELECT ================= @@ -326,11 +401,49 @@ class Asset extends CI_Controller { echo json_encode($data); } - public function get_items(){ - $data = $this->db - ->where('stok > 0') - ->get('items') - ->result(); + public function get_items() + { + // $data = $this->db + // ->select('items.*, kode_barang.*') + // ->from('items') + // ->join('kode_barang', 'items.kode_id = kode_barang.id', 'left') + // ->where('items.stok >', 0) + // ->get() + // ->result(); + + $this->db->select(' + items.id, + items.nama_barang, + items.harga_jual, + items.harga_beli, + items.kode_detail, + kode_barang.tracking_type, + 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' , + 'left'); + + $this->db->join('kode_barang', + 'items.kode_id = kode_barang.id' , + 'left'); + + // $this->db->where('items.account_id', $account_id); + + $this->db->group_by('items.id'); + + // ✅ INI KUNCINYA + $this->db->having('stok >', 0); + + + $data = $this->db->get()->result(); + echo json_encode($data); } diff --git a/application/controllers/Cronjob.php b/application/controllers/Cronjob.php index 3d1ba37..020ff5a 100644 --- a/application/controllers/Cronjob.php +++ b/application/controllers/Cronjob.php @@ -9,9 +9,9 @@ class Cronjob extends CI_Controller { $this->load->database(); // ONLY CLI ACCESS - if (!$this->input->is_cli_request()) { - exit('No direct access allowed'); - } + // if (!$this->input->is_cli_request()) { + // exit('No direct access allowed'); + // } } public function generate_insight() @@ -353,4 +353,320 @@ class Cronjob extends CI_Controller { } } -} \ No newline at end of file + // ===================================================== + // CRONJOB AUTO PENYUSUTAN ASSET BULANAN + // ===================================================== + public function depreciation_asset() + { + $period = date('Y-m'); + $runDate = date('Y-m-d'); + $akun_beban_penyusutan = 54; + + $assets = $this->db + ->select('id, nama_asset, nilai_perolehan, nilai_buku, nilai_residu, penyusutan_per_bulan, masa_manfaat, tanggal_perolehan, akumulasi_penyusutan, account_debit_id') + ->from('assets') + ->where('status', 'aktif') + ->where('nilai_buku > nilai_residu', null, false) + ->where('penyusutan_per_bulan >', 0) + ->get() + ->result(); + + $processed = 0; + $success = 0; + $skipped = 0; + $errors = 0; + $details = []; + + $this->db->trans_begin(); + + try { + foreach ($assets as $a) { + $processed++; + + $assetDetails = [ + 'asset_id' => (int)$a->id, + 'asset_name' => $a->nama_asset, + 'processed_months' => [], + 'skipped_months' => [] + ]; + + $account_parent = $this->db + ->select('id') + ->where('parent_id', $a->account_debit_id) + ->get('accounts') + ->row(); + + if (!$account_parent || empty($account_parent->id)) { + $skipped++; + $assetDetails['status'] = 'skip'; + $assetDetails['reason'] = 'account_parent_not_found'; + $details[] = $assetDetails; + continue; + } + + $akun_akumulasi_penyusutan = (int)$account_parent->id; + + $mutasiPatokan = $this->db + ->select('tanggal_dibuat') + ->from('asset_mutations') + ->where('asset_id', $a->id) + ->where_in('tipe', ['perolehan', 'penambahan']) + ->order_by('tanggal_dibuat', 'DESC') + ->order_by('id', 'DESC') + ->limit(1) + ->get() + ->row(); + + $tanggalPatokan = null; + if ($mutasiPatokan && !empty($mutasiPatokan->tanggal_dibuat)) { + $tanggalPatokan = $mutasiPatokan->tanggal_dibuat; + } + + if (empty($tanggalPatokan)) { + $tanggalPatokan = $a->tanggal_perolehan; + } + + if (empty($tanggalPatokan)) { + $skipped++; + $assetDetails['status'] = 'skip'; + $assetDetails['reason'] = 'tanggal_patokan_tidak_ditemukan'; + $details[] = $assetDetails; + continue; + } + + $startMonth = date('Y-m', strtotime(date('Y-m-01', strtotime($tanggalPatokan)) . ' +1 month')); + $currentMonth = $period; + + if ($startMonth > $currentMonth) { + $skipped++; + $assetDetails['status'] = 'skip'; + $assetDetails['reason'] = 'start_next_month_after_acquisition'; + $assetDetails['tanggal_patokan'] = $tanggalPatokan; + $details[] = $assetDetails; + continue; + } + + $nilaiPerolehan = (float)$a->nilai_perolehan; + $nilaiResidu = (float)$a->nilai_residu; + $susutPerBulan = (float)$a->penyusutan_per_bulan; + $masaManfaat = (int)$a->masa_manfaat; + + $akumulasiMutasi = $this->db + ->select('COALESCE(SUM(nilai),0) total', false) + ->from('asset_mutations') + ->where('asset_id', $a->id) + ->where('tipe', 'penyusutan') + ->get() + ->row(); + + $akumulasiBerjalan = (float)($akumulasiMutasi ? $akumulasiMutasi->total : 0); + $nilaiBukuBerjalan = $nilaiPerolehan - $akumulasiBerjalan; + + if ($nilaiBukuBerjalan < $nilaiResidu) { + $nilaiBukuBerjalan = $nilaiResidu; + $akumulasiBerjalan = $nilaiPerolehan - $nilaiBukuBerjalan; + } + + $iterMonth = $startMonth; + $assetHasSuccess = false; + + while ($iterMonth <= $currentMonth) { + $monthsUsed = 0; + $start = new DateTime(date('Y-m-01', strtotime($tanggalPatokan))); + $end = new DateTime(date('Y-m-01', strtotime($iterMonth . '-01'))); + $diff = $start->diff($end); + $monthsUsed = ($diff->y * 12) + $diff->m; + + if ($monthsUsed < 0) { + $skipped++; + $assetDetails['skipped_months'][] = [ + 'period' => $iterMonth, + 'reason' => 'tanggal_patokan_after_period' + ]; + $iterMonth = date('Y-m', strtotime($iterMonth . '-01 +1 month')); + continue; + } + + if ($masaManfaat > 0 && $monthsUsed >= $masaManfaat) { + $skipped++; + $assetDetails['skipped_months'][] = [ + 'period' => $iterMonth, + 'reason' => 'masa_manfaat_finished' + ]; + break; + } + + $acqDay = (int)date('d', strtotime($tanggalPatokan)); + $lastDayOfMonth = (int)date('t', strtotime($iterMonth . '-01')); + $dueDay = min($acqDay, $lastDayOfMonth); + $dueDateThisMonth = $iterMonth . '-' . str_pad($dueDay, 2, '0', STR_PAD_LEFT); + + // Untuk bulan berjalan, tetap hormati due date. + // Untuk bulan yang sudah lewat, langsung boleh diproses (catch-up). + if ($iterMonth === $currentMonth && strtotime($runDate) < strtotime($dueDateThisMonth)) { + $skipped++; + $assetDetails['skipped_months'][] = [ + 'period' => $iterMonth, + 'reason' => 'not_due_date_today', + 'due_date' => $dueDateThisMonth, + 'run_date' => $runDate + ]; + $iterMonth = date('Y-m', strtotime($iterMonth . '-01 +1 month')); + continue; + } + + $startDate = $iterMonth . '-01'; + $endDate = date('Y-m-d', strtotime($startDate . ' +1 month')); + + $already = $this->db + ->where('asset_id', $a->id) + ->where('tipe', 'penyusutan') + ->where('tanggal_dibuat >=', $startDate) + ->where('tanggal_dibuat <', $endDate) + ->count_all_results('asset_mutations'); + + if ($already > 0) { + $skipped++; + $assetDetails['skipped_months'][] = [ + 'period' => $iterMonth, + 'reason' => 'already_depreciated_this_month' + ]; + $iterMonth = date('Y-m', strtotime($iterMonth . '-01 +1 month')); + continue; + } + + if ($nilaiBukuBerjalan <= $nilaiResidu) { + $skipped++; + $assetDetails['skipped_months'][] = [ + 'period' => $iterMonth, + 'reason' => 'nothing_to_depreciate' + ]; + break; + } + + $maxSusut = max(0, $nilaiBukuBerjalan - $nilaiResidu); + $susut = min($susutPerBulan, $maxSusut); + + if ($susut <= 0) { + $skipped++; + $assetDetails['skipped_months'][] = [ + 'period' => $iterMonth, + 'reason' => 'nothing_to_depreciate' + ]; + break; + } + + $akumulasiBefore = $akumulasiBerjalan; + $nilaiBukuBefore = $nilaiBukuBerjalan; + + $akumulasiBerjalan += $susut; + $nilaiBukuBerjalan = $nilaiPerolehan - $akumulasiBerjalan; + + if ($nilaiBukuBerjalan < $nilaiResidu) { + $nilaiBukuBerjalan = $nilaiResidu; + $akumulasiBerjalan = $nilaiPerolehan - $nilaiBukuBerjalan; + } + + $this->db->insert('asset_mutations', [ + 'asset_id' => $a->id, + 'tipe' => 'penyusutan', + 'nilai' => $susut, + 'keterangan' => 'Penyusutan otomatis periode ' . $iterMonth . ' (cronjob)', + 'tanggal_dibuat' => $dueDateThisMonth + ]); + + $no_ref = 'DEP-' . date('YmdHis') . '-' . $a->id . '-' . str_replace('-', '', $iterMonth); + $this->db->insert('journals', [ + 'tanggal' => $dueDateThisMonth, + 'no_ref' => $no_ref, + 'keterangan' => 'Penyusutan Asset ' . $a->nama_asset . ' periode ' . $iterMonth, + 'ref_type' => 'assets', + 'ref_id' => $a->id, + 'created_by' => 1 + ]); + + $jid = $this->db->insert_id(); + + $this->db->insert('journal_details', [ + 'journal_id' => $jid, + 'account_id' => $akun_beban_penyusutan, + 'debit' => $susut, + 'kredit' => 0 + ]); + + $this->db->insert('journal_details', [ + 'journal_id' => $jid, + 'account_id' => $akun_akumulasi_penyusutan, + 'debit' => 0, + 'kredit' => $susut + ]); + + $success++; + $assetHasSuccess = true; + + $assetDetails['processed_months'][] = [ + 'period' => $iterMonth, + 'depreciation' => (float)$susut, + 'due_date' => $dueDateThisMonth, + 'akumulasi_before' => (float)$akumulasiBefore, + 'akumulasi_after' => (float)$akumulasiBerjalan, + 'nilai_buku_before' => (float)$nilaiBukuBefore, + 'nilai_buku_after' => (float)$nilaiBukuBerjalan, + 'journal_no_ref' => $no_ref + ]; + + $iterMonth = date('Y-m', strtotime($iterMonth . '-01 +1 month')); + } + + if ($assetHasSuccess) { + $this->db->where('id', $a->id)->update('assets', [ + 'akumulasi_penyusutan' => $akumulasiBerjalan, + 'nilai_buku' => $nilaiBukuBerjalan + ]); + + $assetDetails['status'] = 'success'; + $assetDetails['akumulasi_final'] = (float)$akumulasiBerjalan; + $assetDetails['nilai_buku_final'] = (float)$nilaiBukuBerjalan; + } else { + $assetDetails['status'] = 'skip'; + if (empty($assetDetails['reason'])) { + $assetDetails['reason'] = 'no_eligible_month_to_process'; + } + } + + $details[] = $assetDetails; + } + + if ($this->db->trans_status() === FALSE) { + throw new Exception('Transaction failed'); + } + + $this->db->trans_commit(); + + echo json_encode([ + 'status' => true, + 'period' => $period, + 'processed' => $processed, + 'success' => $success, + 'skipped' => $skipped, + 'errors' => $errors, + 'message' => 'Depreciation cron executed', + 'details' => $details + ]); + } catch (Exception $e) { + $this->db->trans_rollback(); + $errors++; + + echo json_encode([ + 'status' => false, + 'period' => $period, + 'processed' => $processed, + 'success' => $success, + 'skipped' => $skipped, + 'errors' => $errors, + 'message' => $e->getMessage() + ]); + } + } + +} diff --git a/application/controllers/Customers.php b/application/controllers/Customers.php index af6db61..511b866 100644 --- a/application/controllers/Customers.php +++ b/application/controllers/Customers.php @@ -135,6 +135,8 @@ class Customers extends CI_Controller { // ========================= public function delete($id) { + $customer = $this->dm->setTable('customers')->get_by_id($id); + $this->dm->setTable('customers')->delete($id); // ================= LOG ACTIVITY ================= diff --git a/application/controllers/Employees.php b/application/controllers/Employees.php index 6229d12..7b1f03c 100644 --- a/application/controllers/Employees.php +++ b/application/controllers/Employees.php @@ -10,6 +10,10 @@ class Employees extends CI_Controller { $this->load->database(); $this->load->helper(['url', 'file']); + if (!$this->session->userdata('logged_in')) { + redirect('auth'); + } + date_default_timezone_set('Asia/Jakarta'); } @@ -192,6 +196,14 @@ class Employees extends CI_Controller { $data['photo'] = $photo; $this->db->insert('k_employees',$data); + $employee_id = $this->db->insert_id(); + + log_activity( + 'employees', + 'create', + 'Menambah karyawan: '.$data['full_name'].' (ID: '.$employee_id.')', + 'success' + ); return $this->json([ 'status' => true, @@ -247,6 +259,13 @@ class Employees extends CI_Controller { ->where('id',$id) ->update('k_employees',$data); + log_activity( + 'employees', + 'update', + 'Update karyawan ID: '.$id.' Nama: '.($data['full_name'] ?? '-'), + 'success' + ); + return $this->json([ 'status' => true, 'message' => 'Berhasil update data' @@ -292,6 +311,13 @@ class Employees extends CI_Controller { 'id'=>$id ]); + log_activity( + 'employees', + 'delete', + 'Hapus karyawan ID: '.$id.' Nama: '.($employee->full_name ?? '-'), + 'success' + ); + return $this->json([ 'status' => true, 'message' => 'Berhasil hapus data' diff --git a/application/controllers/Generateneracasaldo.php b/application/controllers/Generateneracasaldo.php index c59e05c..10a5c6c 100644 --- a/application/controllers/Generateneracasaldo.php +++ b/application/controllers/Generateneracasaldo.php @@ -27,114 +27,81 @@ class Generateneracasaldo extends CI_Controller { } // ========================= - // 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; - } - - // ========================= - // AMBIL DATA NERACA SALDO (DIPAKAI BERSAMA OLEH get_data() & generatepdf()) + // AMBIL DATA NERACA SALDO + // DIPAKAI OLEH AJAX & PDF // ========================= private function get_neracasaldo_data() { - $this->db->select(' + $tanggal_awal = $this->input->get('tanggal_awal'); + $tanggal_akhir = $this->input->get('tanggal_akhir'); + + $this->db->select(" + accounts.id, accounts.kode_akun, accounts.nama_akun, + accounts.kategori, accounts.tipe, - accounts.posisi, - COALESCE(SUM(journal_details.debit),0) as debit, - COALESCE(SUM(journal_details.kredit),0) as kredit - '); + 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'); + // ====================================== + // FILTER PERIODE + // ====================================== + if (!empty($tanggal_awal) && !empty($tanggal_akhir)) { - $this->db->group_by('accounts.id'); - $this->db->order_by('accounts.kode_akun','ASC'); + $this->db->join( + "(SELECT jd.* + FROM journal_details jd + JOIN journals j ON j.id = jd.journal_id + WHERE j.tanggal BETWEEN ".$this->db->escape($tanggal_awal)." + AND ".$this->db->escape($tanggal_akhir)." + ) journal_details", + "journal_details.account_id = accounts.id", + "left", + false + ); - $rows = $this->db->get()->result(); + } else { - // ========================= - // HITUNG LABA BERJALAN - // ========================= - $laba = $this->get_laba_berjalan(); + $this->db->join( + 'journal_details', + 'journal_details.account_id = accounts.id', + 'left' + ); + + } + + $rows = $this->db + ->where('accounts.is_active',1) + ->group_by('accounts.id') + ->order_by('accounts.kode_akun','ASC') + ->get() + ->result(); - $result = []; $total_debit = 0; $total_kredit = 0; - foreach ($rows as $r) { + foreach($rows as $row){ - // ================= SALDO NORMAL - if ($r->posisi == 'debit') { - $saldo = $r->debit - $r->kredit; - } else { - $saldo = $r->kredit - $r->debit; - } + $row->debit = (float)$row->debit; + $row->kredit = (float)$row->kredit; - // ================= 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); - } - - $total_debit += $r->saldo_debit; - $total_kredit += $r->saldo_kredit; - - $result[] = [ - 'kode' => $r->kode_akun, - 'nama' => $r->nama_akun, - 'saldo_debit' => $r->saldo_debit, - 'saldo_kredit' => $r->saldo_kredit, - ]; + $total_debit += $row->debit; + $total_kredit += $row->kredit; } return [ - 'rows' => $rows, - 'data' => $result, - 'total_debit' => $total_debit, - 'total_kredit' => $total_kredit, + 'tanggal_awal' => $tanggal_awal, + 'tanggal_akhir' => $tanggal_akhir, + 'rows' => $rows, + 'total_debit' => $total_debit, + 'total_kredit' => $total_kredit ]; } - // ========================= - // GET DATA NERACA SALDO (JSON, untuk AJAX/datatable) - // ========================= public function get_data() { $result = $this->get_neracasaldo_data(); @@ -142,68 +109,80 @@ class Generateneracasaldo extends CI_Controller { echo json_encode($result['rows']); } - // ========================= - // PDF GENERATE — NERACA SALDO - // ========================= public function generatepdf() { if ($this->session->userdata('role') != 'Admin') { - show_error('Akses ditolak', 403); + show_error('Akses ditolak',403); } if (ob_get_length()) { ob_end_clean(); } - // ============================================================= - // AMBIL DATA - // ============================================================= + // ========================================== + // DATA + // ========================================== $neracasaldo = $this->get_neracasaldo_data(); - // ============================================================= - // DATA PERUSAHAAN - // ============================================================= + // ========================================== + // PERUSAHAAN + // ========================================== $company = $this->db->get('pengaturan')->row(); - // ============================================================= - // BUILD STRING PERIODE - // ============================================================= - $periodeStr = 'Per Tanggal : ' . date('d-m-Y'); + // ========================================== + // PERIODE + // ========================================== + if(!empty($neracasaldo['tanggal_awal']) && !empty($neracasaldo['tanggal_akhir'])){ - // ============================================================= - // LOAD LIBRARY PDF - // ============================================================= - require_once(APPPATH . 'third_party/fpdf/fpdf.php'); - require_once(APPPATH . 'libraries/PDF_Neracasaldo.php'); + $periodeStr = 'Periode : ' + . date('d-m-Y',strtotime($neracasaldo['tanggal_awal'])) + .' s/d ' + . date('d-m-Y',strtotime($neracasaldo['tanggal_akhir'])); - $pdf = new PDF ('P', 'mm', 'A4'); - $pdf->SetMargins(10, 10, 10); + }else{ + + $periodeStr = 'Seluruh Periode'; + + } + + // ========================================== + // PDF + // ========================================== + require_once(APPPATH.'third_party/fpdf/fpdf.php'); + require_once(APPPATH.'libraries/PDF_Neracasaldo.php'); + + $pdf = new PDF('P','mm','A4'); + $pdf->SetMargins(10,10,10); $pdf->setHeaderData( $company->nama_perusahaan, $company->alamat_perusahaan, - FCPATH . 'uploads/img/logo-ljn.png' + FCPATH.'uploads/img/logo-ljn.png' ); $pdf->setInfoLaporan($periodeStr); - $pdf->AddPage('P', 'A4'); + $pdf->AddPage(); + $pdf->NeracaSaldoTable( - $neracasaldo['data'], + $neracasaldo['rows'], $neracasaldo['total_debit'], $neracasaldo['total_kredit'] ); + $pdf->NeracaSaldoRingkasan( $neracasaldo['total_debit'], $neracasaldo['total_kredit'] ); - $pdf->Pembuat('Tito Ngudiana', 'Finance Department'); - // ============================================================= - // OUTPUT - // ============================================================= - $filename = 'Neraca_Saldo_' . date('YmdHis') . '.pdf'; - $pdf->Output('I', $filename); + $pdf->Pembuat( + $this->session->userdata('nama') ?: 'Administrator', + 'Finance Department' + ); + + $filename = 'Neraca_Saldo_'.date('YmdHis').'.pdf'; + + $pdf->Output('I',$filename); exit; } } \ No newline at end of file diff --git a/application/controllers/Invoices.php b/application/controllers/Invoices.php index 46c0f0c..eecdce8 100644 --- a/application/controllers/Invoices.php +++ b/application/controllers/Invoices.php @@ -367,7 +367,9 @@ class Invoices extends CI_Controller { 'warehouse_id' => null, 'keterangan' => $inv_items->keterangan, 'subtotal_asli' => $inv_items->subtotal_asli, - 'is_cicilan' => $inv_items->is_cicilan + 'is_cicilan' => $inv_items->is_cicilan, + 'source_cicilan_id' => $c->id, + 'total_hpp_barang' => $c->cicilan_hpp_barang ]; $this->db->insert('invoice_details', $insert_data); @@ -476,13 +478,14 @@ class Invoices extends CI_Controller { public function detail($id) { // GET customer_id - $row = $this->db->select('customer_id') + $row = $this->db->select('customer_id, no_invoice') ->where('id',$id) ->get('invoices') ->row(); $data['active_menu'] = "invoice_draft"; $data['invoice_id'] = $id; + $data['no_invoice'] = $row ? $row->no_invoice : null; $data['customer_id'] = $row ? $row->customer_id : null; $this->load->view('partials/header', $data); @@ -554,6 +557,7 @@ class Invoices extends CI_Controller { $harga = (float)$this->input->post('harga'); $account_id = $this->input->post('account_id'); $warehouse_id = $this->input->post('warehouse_id'); + $barcode_id = $this->input->post('barcode_id'); $total_harga = $qty * $harga; $total_hpp = 0; @@ -561,7 +565,9 @@ class Invoices extends CI_Controller { // jika di bayar cicilan $is_cicilan = $this->input->post('is_cicilan') ?? null; $tenor = (int)$this->input->post('tenor') ?? 0; - + + // ambil invoice Detail + $invoice = $this->db->get_where('invoices', ['id' => $invoice_id])->row(); // ========================= // VALIDASI & AMBIL ITEM // ========================= @@ -576,12 +582,44 @@ class Invoices extends CI_Controller { $nama_item = $item->nama_barang; // cek stok - $stok = $this->get_stok($item_id); - if($stok < $qty){ - echo json_encode(['status'=>false, 'message'=>'Stok tidak cukup. Sisa: '.$stok]); + // $stok = $this->get_stok($item_id); + // if($stok < $qty){ + // echo json_encode(['status'=>false, 'message'=>'Stok tidak cukup. Sisa: '.$stok]); + // $this->db->trans_rollback(); + // return; + // } + + // START - Item Movement & item barcodes update status + $this->db->where('item_id', $item_id); + $this->db->order_by('created_at', 'DESC'); + $this->db->limit(1); + $item_move = $this->db->get('item_movements')->row(); + + // get item_barcode + $itemBar = $this->db->get_where('item_barcodes', ['id'=>$barcode_id])->row(); + + if($itemBar->qty_sisa < $qty) { + echo json_encode(['status'=>false, 'message'=>'Stok tidak cukup. Sisa: '.$itemBar->qty_sisa]); $this->db->trans_rollback(); return; } + + $total_sisa = $itemBar->qty_sisa - $qty; + $status_bar = ($total_sisa == 0) ? 'sold_out' : 'available'; + + $this->db->insert('item_movements',[ + 'item_id' => $item_id, + 'barcode_id' => $barcode_id, + 'qty' => $qty, + 'from_type' => $item_move->to_type, //enum('supplier','warehouse','technician','customer','invoice') + 'to_type' => 'invoice', //enum('supplier','warehouse','technician','customer','invoice') + 'movement_type' => 'sold_out', //enum('purchase','transfer','installation','uninstallation','supplier_return','customer_return','adjustment','sold_out') + 'notes' => 'Barang terjual ke INV : ' . $invoice->no_invoice, + 'created_at' => date('Y-m-d H:i:s') + ]); + + $this->db->where('id', $barcode_id)->update('item_barcodes', ['status' => $status_bar, 'qty_sisa' => $total_sisa]); + // END - Item Movement & item barcodes update status $total_hpp = $qty * $item->harga_beli; } @@ -600,6 +638,9 @@ class Invoices extends CI_Controller { // harga per cicilan $harga_cicilan = round($total_harga / $tenor); + + //ngatur HPP Cicilan + $total_hpp = round($total_hpp / $tenor); $harga_simpan = $harga_cicilan; $subtotal_simpan = $harga_cicilan; @@ -624,7 +665,8 @@ class Invoices extends CI_Controller { 'keterangan' => $this->input->post('keterangan'), 'subtotal_asli' => $harga_asli, 'is_cicilan' => $is_cicilan ? 1 : 0, - 'total_hpp_barang' => $total_hpp + 'total_hpp_barang' => $total_hpp, + 'barcode_id' => $barcode_id ]; $this->db->insert('invoice_details', $insert_data); @@ -639,8 +681,6 @@ class Invoices extends CI_Controller { $this->update_total($invoice_id); - // ambil customer dari invoice - $invoice = $this->db->get_where('invoices', ['id' => $invoice_id])->row(); if ($is_cicilan && $tenor > 0) { @@ -653,6 +693,7 @@ class Invoices extends CI_Controller { $tenor, $tanggal, $account_id, + $total_hpp, $item_id ?: null ); } @@ -675,105 +716,108 @@ class Invoices extends CI_Controller { } } - // ========================= - // UPDATE ITEM - RESPONSE KONSISTEN - // ========================= - public function update_item() - { - $this->db->trans_begin(); + // // ========================= + // // UPDATE ITEM - RESPONSE KONSISTEN + // // ========================= + // public function update_item() + // { + // $this->db->trans_begin(); - $id = $this->input->post('id'); - $old = $this->db->get_where('invoice_details', ['id'=>$id])->row(); + // $id = $this->input->post('id'); + // $old = $this->db->get_where('invoice_details', ['id'=>$id])->row(); - if(!$old){ - echo json_encode(['status'=>false, 'message'=>'Item tidak ditemukan']); - return; - } + // if(!$old){ + // echo json_encode(['status'=>false, 'message'=>'Item tidak ditemukan']); + // return; + // } - $cek = $this->db->get_where('invoice_installments', [ - 'invoice_detail_id' => $id - ])->num_rows(); + // $cek = $this->db->get_where('invoice_installments', [ + // 'invoice_detail_id' => $id + // ])->num_rows(); - if ($cek > 0) { - echo json_encode([ - 'status' => false, - 'message' => 'Item cicilan tidak bisa diubah. Hapus dan buat ulang.' - ]); - return; - } + // if ($cek > 0) { + // echo json_encode([ + // 'status' => false, + // 'message' => 'Item cicilan tidak bisa diubah. Hapus dan buat ulang.' + // ]); + // return; + // } - // 🔥 REVERSE DATA LAMA - $this->delete_jurnal_item($id); - $this->delete_stok_item($id); + // // 🔥 REVERSE DATA LAMA + // $this->delete_jurnal_item($id); + // $this->delete_stok_item($id); - // DATA BARU - $item_id = $this->input->post('item_id'); - $nama_item = $this->input->post('nama_item'); - $tanggal = $this->input->post('tanggal'); + // // DATA BARU + // $item_id = $this->input->post('item_id'); + // $nama_item = $this->input->post('nama_item'); + // $tanggal = $this->input->post('tanggal'); - $total_hpp = 0; + // $total_hpp = 0; - if($item_id){ - $item = $this->db->get_where('items', ['id'=>$item_id])->row(); - if($item){ - $nama_item = $item->nama_barang; - $stok = $this->get_stok($item_id); - $qty = (float)$this->input->post('qty'); - $total_hpp = $this->input->post('qty') * $item->harga_beli; - if($stok < $qty){ - echo json_encode(['status'=>false, 'message'=>'Stok tidak cukup']); - return; - } - } - } + // if($item_id){ + // $item = $this->db->get_where('items', ['id'=>$item_id])->row(); + // if($item){ + // $nama_item = $item->nama_barang; + // $stok = $this->get_stok($item_id); + // $qty = (float)$this->input->post('qty'); + // $total_hpp = $this->input->post('qty') * $item->harga_beli; + // if($stok < $qty){ + // echo json_encode(['status'=>false, 'message'=>'Stok tidak cukup']); + // return; + // } + + + // } + + // } - $qty = (float)$this->input->post('qty'); - $harga = (float)$this->input->post('harga'); + // $qty = (float)$this->input->post('qty'); + // $harga = (float)$this->input->post('harga'); - // UPDATE - $update_data = [ - 'tanggal' => $tanggal, - 'account_id' => $this->input->post('account_id'), - 'items_id' => $item_id ?: null, - 'nama_item' => $nama_item, - 'qty' => $qty, - 'harga' => $harga, - 'subtotal' => $qty * $harga, - 'warehouse_id' => $this->input->post('warehouse_id'), - 'keterangan' => $this->input->post('keterangan'), - 'total_hpp_barang' => $total_hpp - ]; + // // UPDATE + // $update_data = [ + // 'tanggal' => $tanggal, + // 'account_id' => $this->input->post('account_id'), + // 'items_id' => $item_id ?: null, + // 'nama_item' => $nama_item, + // 'qty' => $qty, + // 'harga' => $harga, + // 'subtotal' => $qty * $harga, + // 'warehouse_id' => $this->input->post('warehouse_id'), + // 'keterangan' => $this->input->post('keterangan'), + // 'total_hpp_barang' => $total_hpp + // ]; - $this->db->where('id', $id)->update('invoice_details', $update_data); + // $this->db->where('id', $id)->update('invoice_details', $update_data); - // GENERATE ULANG - if($this->input->post('account_id') != 70) { - $this->generate_jurnal_item($id); - } - $this->update_total($old->invoice_id); + // // GENERATE ULANG + // if($this->input->post('account_id') != 70) { + // $this->generate_jurnal_item($id); + // } + // $this->update_total($old->invoice_id); - // ✅ RESPONSE KONSISTEN DENGAN JS - if ($this->db->trans_status() === FALSE){ - $this->db->trans_rollback(); - echo json_encode(['status'=>false, 'message'=>'Update gagal']); - } else { - $this->db->trans_commit(); + // // ✅ RESPONSE KONSISTEN DENGAN JS + // if ($this->db->trans_status() === FALSE){ + // $this->db->trans_rollback(); + // echo json_encode(['status'=>false, 'message'=>'Update gagal']); + // } else { + // $this->db->trans_commit(); - log_activity( - 'invoice', - 'update_item', - 'Update item invoice detail ID ' . $id, - 'success' - ); + // log_activity( + // 'invoice', + // 'update_item', + // 'Update item invoice detail ID ' . $id, + // 'success' + // ); - echo json_encode(['status'=>true, 'message'=>'Item berhasil diupdate']); - } - } + // echo json_encode(['status'=>true, 'message'=>'Item berhasil diupdate']); + // } + // } // ========================= // DELETE ITEM - RESPONSE KONSISTEN // ========================= - public function delete_item($id) + public function delete_item($id, $no_invoice) { $this->db->trans_begin(); @@ -792,6 +836,36 @@ class Invoices extends CI_Controller { $this->delete_stok_item($id); $this->db->delete('invoice_details', ['id'=>$id]); $this->update_total($d->invoice_id); + + + if ($d->items_id AND $d->is_cicilan != 1) { + // START - Item Movement & item barcodes update status + $this->db->where('item_id', $d->items_id); + $this->db->order_by('created_at', 'DESC'); + $this->db->limit(1); + $item_move = $this->db->get('item_movements')->row(); + + // get item_barcode + $itemBar = $this->db->get_where('item_barcodes', ['id'=>$d->barcode_id])->row(); + + $total_sisa = $itemBar->qty_sisa + $d->qty; + $status_bar = ($total_sisa == 0) ? 'sold_out' : 'available'; + + $this->db->insert('item_movements',[ + 'item_id' => $d->items_id, + 'barcode_id' => $d->barcode_id, + 'qty' => $d->qty, + 'from_type' => $item_move->to_type, //enum('supplier','warehouse','technician','customer','invoice') + 'to_type' => 'warehouse', //enum('supplier','warehouse','technician','customer','invoice') + 'movement_type' => 'purchase', //enum('purchase','transfer','installation','uninstallation','supplier_return','customer_return','adjustment','sold_out') + 'notes' => 'Barang di kembalikan ke gudang dari : ' . $no_invoice, + 'created_at' => date('Y-m-d H:i:s') + ]); + + // $this->db->where('id', $d->barcode_id)->update('item_barcodes', ['status' => 'available']); + $this->db->where('id', $d->barcode_id)->update('item_barcodes', ['status' => $status_bar, 'qty_sisa' => $total_sisa]); + // END - Item Movement & item barcodes update status + } // ✅ RESPONSE KONSISTEN DENGAN JS if ($this->db->trans_status() === FALSE){ @@ -1223,7 +1297,7 @@ class Invoices extends CI_Controller { } - private function generate_installments($invoice_id, $detail_id, $customer_id, $total, $tenor, $start_date, $account_id, $item_id = null) + private function generate_installments($invoice_id, $detail_id, $customer_id, $total, $tenor, $start_date, $account_id, $total_hpp, $item_id = null) { $nominal = round($total / $tenor); $sisa = $total - ($nominal * $tenor); @@ -1250,6 +1324,7 @@ class Invoices extends CI_Controller { 'nominal' => $nilai, 'account_id' => $account_id, 'item_id' => $item_id ?: null, + 'cicilan_hpp_barang' => $total_hpp, 'status' => 'unpaid', 'is_billed' => $is_billed ]); @@ -1336,7 +1411,8 @@ class Invoices extends CI_Controller { 'keterangan' => $inv_items->keterangan, 'subtotal_asli' => $inv_items->subtotal_asli, 'is_cicilan' => $inv_items->is_cicilan, - 'source_cicilan_id' => $cicilan->id + 'source_cicilan_id' => $cicilan->id, + 'total_hpp_barang' => $cicilan->cicilan_hpp_barang ]); // ========================= @@ -1498,6 +1574,7 @@ public function bayar() invoice_details.recognized_amount, invoice_details.total_hpp_barang, invoice_details.recognized_hpp, + invoice_details.is_cicilan, accounts.priority ') ->from('invoice_details') diff --git a/application/controllers/Items.php b/application/controllers/Items.php index a28302b..ce2329c 100644 --- a/application/controllers/Items.php +++ b/application/controllers/Items.php @@ -40,6 +40,98 @@ class Items extends CI_Controller { $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 @@ -294,92 +386,103 @@ class Items extends CI_Controller { $this->load_data_barcode($barcode->item_id); } -public function get_barcode_item() -{ - $barcode = trim($this->input->post('barcode')); + 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; + } - if ($barcode == '') { echo json_encode([ - 'status' => false, - 'message' => 'Barcode kosong.' + 'status'=>true, + 'data'=>$row ]); - 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.barcode',$barcode) - ->get() - ->row(); + public function get_list_barcode_id($item_id) + { + // $item_id = trim($this->input->post('item_id')); - if(!$row){ - echo json_encode([ - 'status'=>false, - 'message'=>'Barcode tidak ditemukan.' - ]); - return; + 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); } - 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, - 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.status','warehouse') - ->get() - ->result(); - - if(!$row){ - echo json_encode([ - 'status'=>false, - 'message'=>'Item barang tidak ditemukan.' - ]); - return; - } - - echo json_encode($row); -} - // ========================= // GET DATA (FIX) @@ -484,7 +587,7 @@ public function get_list_barcode_id($item_id) } else { $btnAdjust = ''; } - $action = $btnDetail . $btnEdit . $btnAdjust . $btnDelete . $btnSN; + $action = $btnDetail . $btnEdit . $btnAdjust . $btnSN; } @@ -578,7 +681,8 @@ public function get_list_barcode_id($item_id) if(!$nama_barang || $qty <= 0 || $harga_beli <= 0 || !$warehouse_id || !$account_kas){ echo json_encode([ 'status'=>false, - 'message'=>'Data tidak lengkap / tidak valid' + 'message'=>'Data tidak lengkap / tidak valid', + 'account_id' => $account_kas ]); return; } @@ -655,7 +759,9 @@ public function get_list_barcode_id($item_id) // ================= Item Barcode Masuk ================= for ($i = 1; $i <= $unit_qty; $i++) { - $barcode = sprintf('%s-%04d', $kode_detail, $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, @@ -680,39 +786,6 @@ public function get_list_barcode_id($item_id) 'created_at' => date('Y-m-d H:i:s') ]); } -// CREATE TABLE `item_barcodes` ( -// `id` int NOT NULL AUTO_INCREMENT, -// `item_id` int NOT NULL, -// `barcode` varchar(100) NOT NULL, -// `serial_number` varchar(100) DEFAULT NULL, -// `warehouse_id` int DEFAULT NULL, -// `qty_awal` decimal(15,2) DEFAULT NULL, -// `qty_sisa` decimal(15,2) DEFAULT NULL, -// `status` enum('available','reserved','installed','damaged','lost','returned_supplier') DEFAULT 'available', -// `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, -// PRIMARY KEY (`id`), -// UNIQUE KEY `uk_barcode` (`barcode`), -// KEY `idx_item` (`item_id`), -// KEY `idx_warehouse` (`warehouse_id`), -// CONSTRAINT `fk_barcode_item` FOREIGN KEY (`item_id`) REFERENCES `items` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE -// ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; - - -// CREATE TABLE `item_movements` ( -// `id` int NOT NULL AUTO_INCREMENT, -// `item_id` int NOT NULL, -// `barcode_id` int DEFAULT NULL, -// `qty` decimal(15,2) NOT NULL DEFAULT '1.00', -// `from_type` enum('supplier','warehouse','technician','customer') DEFAULT NULL, -// `from_id` int DEFAULT NULL, -// `to_type` enum('supplier','warehouse','technician','customer') DEFAULT NULL, -// `to_id` int DEFAULT NULL, -// `movement_type` enum('purchase','transfer','installation','uninstallation','supplier_return','customer_return','adjustment') NOT NULL, -// `reference_type` varchar(50) DEFAULT NULL, -// `reference_id` int DEFAULT NULL, -// `notes` varchar(255) DEFAULT NULL, -// `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, - // ================= JURNAL ================= $no_ref = 'BLI-'.date('Ymd').'-'.$item_id; @@ -737,12 +810,16 @@ public function get_list_barcode_id($item_id) ]); // 🔥 Kredit Kas (DINAMIS) - $this->db->insert('journal_details',[ - 'journal_id'=>$jid, - 'account_id'=>$account_kas, - 'debit'=>0, - 'kredit'=>$total - ]); + 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(); @@ -988,33 +1065,157 @@ public function get_list_barcode_id($item_id) // ========================= public function keluarkan() { - $account_biaya = (int)$this->input->post('account_biaya'); - $tanggal_keluar = $this->input->post('tanggal_keluar'); + $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'); - $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 ') @@ -1022,20 +1223,16 @@ public function get_list_barcode_id($item_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, @@ -1043,10 +1240,9 @@ public function get_list_barcode_id($item_id) '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, @@ -1055,44 +1251,39 @@ public function get_list_barcode_id($item_id) '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']); } @@ -1232,4 +1423,115 @@ public function get_list_barcode_id($item_id) 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 + ") + ->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) { + + // =========================================== + // Hitung jumlah barcode yang seharusnya + // =========================================== + + if ($item->tracking_type == 'UNIT') { + $target = (int)$item->stok; + } else { + $target = (int)floor($item->stok / 1000); + } + + if ($target <= 0) { + continue; + } + + // =========================================== + // Barcode yang sudah ada + // =========================================== + + $barcode_exist = $this->db + ->where('item_id', $item->id) + ->count_all_results('item_barcodes'); + + if ($barcode_exist >= $target) { + continue; + } + + $need = $target - $barcode_exist; + + for ($i = 1; $i <= $need; $i++) { + + $barcode = strtoupper($item->kode_detail) . '-' . strtoupper(bin2hex(random_bytes(2))); + + $qty = ($item->tracking_type == 'UNIT') + ? 1 + : 1000; + + // ============================= + // 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' + ]); + } + } + + 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.' + ]); +} } \ No newline at end of file diff --git a/application/controllers/Kodebarang.php b/application/controllers/Kodebarang.php index ca68418..1580737 100644 --- a/application/controllers/Kodebarang.php +++ b/application/controllers/Kodebarang.php @@ -25,7 +25,7 @@ class Kodebarang extends CI_Controller { // ========================= public function get_data() { - $data = $this->db->get('kode_barang')->result(); + $data = $this->db->order_by('id', 'DESC')->get('kode_barang')->result(); $result = []; $no = 1; @@ -35,7 +35,10 @@ class Kodebarang extends CI_Controller { $no++, $row->kode_barang, $row->nama, - $row->limit_stock, + (int)$row->limit_stock, + strtoupper($row->tracking_type ?? 'UNIT'), + strtolower($row->need_serial_number ?? 'false') === 'true' ? 'Ya' : 'Tidak', + $row->unit ?? 'PCS', ' @@ -61,29 +64,57 @@ class Kodebarang extends CI_Controller { // ========================= public function save() { - $nama = $this->input->post('nama'); - $kode = $this->input->post('kode'); - $limit_stock = $this->input->post('limit_stock'); + $nama = trim($this->input->post('nama')); + $kode = trim($this->input->post('kode')); + $limit_stock = (int)$this->input->post('limit_stock'); + $tracking_type = strtoupper(trim($this->input->post('tracking_type'))); + $need_serial_number = strtolower(trim($this->input->post('need_serial_number'))); + $unit = trim($this->input->post('unit')); - if(!$nama){ - echo json_encode(['status'=>false,'message'=>'Nama wajib diisi']); + if(!$nama || !$kode){ + echo json_encode(['status'=>false,'message'=>'Kode dan nama wajib diisi']); + return; + } + + if(!in_array($tracking_type, ['UNIT','QTY'])){ + echo json_encode(['status'=>false,'message'=>'Tracking type tidak valid']); + return; + } + + if(!in_array($need_serial_number, ['true','false'])){ + echo json_encode(['status'=>false,'message'=>'Need serial number tidak valid']); + return; + } + + if(!in_array($unit, ['PCS','Meter','Lembar'])){ + echo json_encode(['status'=>false,'message'=>'Unit tidak valid']); + return; + } + + $exists = $this->db->get_where('kode_barang', ['kode_barang' => $kode])->num_rows(); + if($exists > 0){ + echo json_encode(['status'=>false,'message'=>'Kode barang sudah digunakan']); return; } $this->db->insert('kode_barang', [ 'kode_barang' => $kode, 'nama' => $nama, - 'limit_stock' => $limit_stock + 'created_at' => date('Y-m-d H:i:s'), + 'limit_stock' => $limit_stock, + 'tracking_type' => $tracking_type, + 'need_serial_number' => $need_serial_number, + 'unit' => $unit ]); log_activity( 'kode_barang', 'create', - 'Menambahkan kode barang: ' . $nama, + 'Menambahkan kode barang: ' . $kode . ' - ' . $nama, 'success' ); - echo json_encode(['status'=>true,'message'=>'Gudang berhasil ditambahkan']); + echo json_encode(['status'=>true,'message'=>'Kode barang berhasil ditambahkan']); } // ========================= @@ -91,30 +122,62 @@ class Kodebarang extends CI_Controller { // ========================= 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'); + $id = (int)$this->input->post('id'); + $kode = trim($this->input->post('kode')); + $nama = trim($this->input->post('nama')); + $limit_stock = (int)$this->input->post('limit_stock'); + $tracking_type = strtoupper(trim($this->input->post('tracking_type'))); + $need_serial_number = strtolower(trim($this->input->post('need_serial_number'))); + $unit = trim($this->input->post('unit')); - if(!$nama){ - echo json_encode(['status'=>false,'message'=>'Nama wajib diisi']); + if(!$id || !$nama || !$kode){ + echo json_encode(['status'=>false,'message'=>'ID, kode dan nama wajib diisi']); + return; + } + + if(!in_array($tracking_type, ['UNIT','QTY'])){ + echo json_encode(['status'=>false,'message'=>'Tracking type tidak valid']); + return; + } + + if(!in_array($need_serial_number, ['true','false'])){ + echo json_encode(['status'=>false,'message'=>'Need serial number tidak valid']); + return; + } + + if(!in_array($unit, ['PCS','Meter','Lembar'])){ + echo json_encode(['status'=>false,'message'=>'Unit tidak valid']); + return; + } + + $exists = $this->db + ->where('kode_barang', $kode) + ->where('id !=', $id) + ->get('kode_barang') + ->num_rows(); + + if($exists > 0){ + echo json_encode(['status'=>false,'message'=>'Kode barang sudah digunakan']); return; } $this->db->where('id',$id)->update('kode_barang', [ 'kode_barang' => $kode, 'nama' => $nama, - 'limit_stock' => $limit_stock + 'limit_stock' => $limit_stock, + 'tracking_type' => $tracking_type, + 'need_serial_number' => $need_serial_number, + 'unit' => $unit ]); log_activity( 'kode_barang', 'update', - 'Update kode barang ID: ' . $id . ' nama: ' . $nama, + 'Update kode barang ID: ' . $id . ' (' . $kode . ' - ' . $nama . ')', 'success' ); - echo json_encode(['status'=>true,'message'=>'Gudang berhasil diupdate']); + echo json_encode(['status'=>true,'message'=>'Kode barang berhasil diupdate']); } // ========================= @@ -122,13 +185,23 @@ class Kodebarang extends CI_Controller { // ========================= public function delete($id) { - // optional: cek relasi ke items - $cek = $this->db->get_where('items',['warehouse_id'=>$id])->num_rows(); + $row = $this->db->get_where('kode_barang', ['id'=>$id])->row(); + + if(!$row){ + echo json_encode([ + 'status'=>false, + 'message'=>'Data kode barang tidak ditemukan' + ]); + return; + } + + // cek relasi ke items (mengacu ke kode_id) + $cek = $this->db->get_where('items',['kode_id'=>$id])->num_rows(); if($cek > 0){ echo json_encode([ 'status'=>false, - 'message'=>'Gudang tidak bisa dihapus karena masih dipakai item' + 'message'=>'Kode barang tidak bisa dihapus karena masih dipakai item' ]); return; } @@ -138,11 +211,11 @@ class Kodebarang extends CI_Controller { log_activity( 'kode_barang', 'delete', - 'Hapus gudang ID: ' . $id, + 'Hapus kode barang ID: ' . $id . ' (' . $row->kode_barang . ' - ' . $row->nama . ')', 'success' ); - echo json_encode(['status'=>true,'message'=>'Gudang berhasil dihapus']); + echo json_encode(['status'=>true,'message'=>'Kode barang berhasil dihapus']); } diff --git a/application/controllers/Neracasaldo.php b/application/controllers/Neracasaldo.php index 2d6aa43..2beea77 100644 --- a/application/controllers/Neracasaldo.php +++ b/application/controllers/Neracasaldo.php @@ -26,87 +26,54 @@ class Neracasaldo extends CI_Controller { $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(' + $tanggal_awal = $this->input->get('tanggal_awal'); + $tanggal_akhir = $this->input->get('tanggal_akhir'); + + $this->db->select(" + accounts.id, accounts.kode_akun, accounts.nama_akun, + accounts.kategori, accounts.tipe, - accounts.posisi, - COALESCE(SUM(journal_details.debit),0) as debit, - COALESCE(SUM(journal_details.kredit),0) as kredit - '); + 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'); + if (!empty($tanggal_awal) && !empty($tanggal_akhir)) { - $this->db->group_by('accounts.id'); - $this->db->order_by('accounts.kode_akun','ASC'); + $this->db->join( + "(SELECT jd.* + FROM journal_details jd + JOIN journals j ON j.id = jd.journal_id + WHERE j.tanggal BETWEEN ".$this->db->escape($tanggal_awal)." + AND ".$this->db->escape($tanggal_akhir)." + ) journal_details", + "journal_details.account_id = accounts.id", + "left", + false + ); - $rows = $this->db->get()->result(); + } else { - // ========================= - // HITUNG LABA BERJALAN - // ========================= - $laba = $this->get_laba_berjalan(); + $this->db->join( + 'journal_details', + 'journal_details.account_id = accounts.id', + 'left' + ); - 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); - } } + $rows = $this->db + ->where('accounts.is_active', 1) + ->group_by('accounts.id') + ->order_by('accounts.kode_akun', 'ASC') + ->get() + ->result(); + echo json_encode($rows); } } \ No newline at end of file diff --git a/application/libraries/PDF_Neracasaldo.php b/application/libraries/PDF_Neracasaldo.php index 61f37f1..ca8c0be 100644 --- a/application/libraries/PDF_Neracasaldo.php +++ b/application/libraries/PDF_Neracasaldo.php @@ -41,49 +41,55 @@ class PDF extends FPDF function Header() { - // Logo (opsional) - if (!empty($this->logo) && file_exists($this->logo)) { - $this->Image($this->logo, 10, 6, 18); - $this->SetXY(31, 10); + // Header lengkap hanya di halaman pertama + if ($this->PageNo() === 1) { + // Logo (opsional) + if (!empty($this->logo) && file_exists($this->logo)) { + $this->Image($this->logo, 10, 6, 18); + $this->SetXY(31, 10); + } else { + $this->SetXY(10, 10); + } + + // Nama perusahaan + $this->SetFont('Arial', 'B', 12); + $this->Cell(0, 6, $this->nama, 0, 1, 'L'); + + // Alamat + $this->SetFont('Arial', '', 9); + $x = !empty($this->logo) && file_exists($this->logo) ? 31 : 10; + $this->SetX($x); + $this->Cell(0, 5, $this->alamat, 0, 1, 'L'); + + // Garis pemisah (A4 Portrait: x=10 s/d x=200) + $this->Ln(2); + $this->SetLineWidth(0.6); + $this->Line(10, $this->GetY(), 200, $this->GetY()); + $this->SetLineWidth(0.2); + $this->Line(10, $this->GetY() + 1, 200, $this->GetY() + 1); + $this->Ln(5); + + // Judul + $this->SetFont('Arial', 'B', 13); + $this->SetTextColor(245, 140, 0); + $this->Cell(0, 7, 'LAPORAN NERACA SALDO', 0, 1, 'C'); + $this->SetTextColor(0, 0, 0); + + // Periode + $this->SetFont('Arial', '', 10); + $this->Cell(0, 5, $this->periodeStr, 0, 1, 'C'); + + // Tanggal cetak + $this->SetFont('Arial', '', 9); + $this->Cell(0, 5, 'Di Cetak Pada : ' . date('d-m-Y') . ' - ' . date('H:i:s'), 0, 1, 'C'); + + $this->Ln(4); } else { - $this->SetXY(10, 10); + // Halaman berikutnya tanpa header laporan + $this->SetY(10); } - // Nama perusahaan - $this->SetFont('Arial', 'B', 12); - $this->Cell(0, 6, $this->nama, 0, 1, 'L'); - - // Alamat - $this->SetFont('Arial', '', 9); - $x = !empty($this->logo) && file_exists($this->logo) ? 31 : 10; - $this->SetX($x); - $this->Cell(0, 5, $this->alamat, 0, 1, 'L'); - - // Garis pemisah (A4 Portrait: x=10 s/d x=200) - $this->Ln(2); - $this->SetLineWidth(0.6); - $this->Line(10, $this->GetY(), 200, $this->GetY()); - $this->SetLineWidth(0.2); - $this->Line(10, $this->GetY() + 1, 200, $this->GetY() + 1); - $this->Ln(5); - - // Judul - $this->SetFont('Arial', 'B', 13); - $this->SetTextColor(245, 140, 0); - $this->Cell(0, 7, 'LAPORAN NERACA SALDO', 0, 1, 'C'); - $this->SetTextColor(0, 0, 0); - - // Periode - $this->SetFont('Arial', '', 10); - $this->Cell(0, 5, $this->periodeStr, 0, 1, 'C'); - - // Tanggal cetak - $this->SetFont('Arial', '', 9); - $this->Cell(0, 5, 'Di Cetak Pada : ' . date('d-m-Y') . ' - ' . date('H:i:s'), 0, 1, 'C'); - - $this->Ln(4); - - // Header kolom tabel + // Header kolom tabel tetap tampil di setiap halaman $this->_drawTableHeader(); } @@ -146,8 +152,12 @@ class PDF extends FPDF $this->Cell(array_sum($this->colWidths), 8, 'Tidak ada data', 1, 1, 'C'); } else { foreach ($rows as $row) { + $namaAkun = isset($row->nama_akun) ? $row->nama_akun : (isset($row['nama_akun']) ? $row['nama_akun'] : ''); + $kodeAkun = isset($row->kode_akun) ? $row->kode_akun : (isset($row['kode_akun']) ? $row['kode_akun'] : ''); + $debit = isset($row->debit) ? (float)$row->debit : (isset($row['debit']) ? (float)$row['debit'] : 0); + $kredit = isset($row->kredit) ? (float)$row->kredit : (isset($row['kredit']) ? (float)$row['kredit'] : 0); - $nbLines = $this->NbLines($this->colWidths[1], $row['nama']); + $nbLines = $this->NbLines($this->colWidths[1], $namaAkun); $rowH = max(1, $nbLines) * $lineH; if ($this->GetY() + $rowH > 270) { @@ -158,14 +168,14 @@ class PDF extends FPDF $this->SetFont('Arial', '', 9); $this->SetXY($startX, $y); - $this->Cell($this->colWidths[0], $rowH, $row['kode'], 1, 0, 'C'); + $this->Cell($this->colWidths[0], $rowH, $kodeAkun, 1, 0, 'C'); $xNama = $this->GetX(); - $this->MultiCell($this->colWidths[1], $lineH, $row['nama'], 1, 'L'); + $this->MultiCell($this->colWidths[1], $lineH, $namaAkun, 1, 'L'); $this->SetXY($xNama + $this->colWidths[1], $y); - $this->Cell($this->colWidths[2], $rowH, $row['saldo_debit'] > 0 ? number_format($row['saldo_debit'], 0, ',', '.') : '-', 1, 0, 'R'); - $this->Cell($this->colWidths[3], $rowH, $row['saldo_kredit'] > 0 ? number_format($row['saldo_kredit'], 0, ',', '.') : '-', 1, 0, 'R'); + $this->Cell($this->colWidths[2], $rowH, $debit > 0 ? number_format($debit, 0, ',', '.') : '-', 1, 0, 'R'); + $this->Cell($this->colWidths[3], $rowH, $kredit > 0 ? number_format($kredit, 0, ',', '.') : '-', 1, 0, 'R'); $this->SetXY($startX, $y + $rowH); } diff --git a/application/views/asset/layout.php b/application/views/asset/layout.php index 34728f7..98e29c7 100644 --- a/application/views/asset/layout.php +++ b/application/views/asset/layout.php @@ -16,9 +16,10 @@ No Tanggal - Kode - Nama + Kode + Nama Lokasi Asset + Keterangan Nilai Masa Susut/Bulan @@ -56,9 +57,10 @@ - + + - + @@ -99,7 +101,8 @@ - + +
- +
+
+
@@ -232,6 +234,7 @@ $(function(){ $('#wrap_item_select').addClass('d-none'); $('#wrap_nama_item').removeClass('d-none'); $('#wrap_qty_item').removeClass('d-none'); + $('#wrap_barcode_select').addClass('d-none'); $('#warehouse_id').html(''); $('#item_select').html(''); @@ -241,10 +244,12 @@ $(function(){ $('#qty').val(''); $('#harga').val(''); $('#keterangan').val(''); + $('#barcode_select').val(''); $('#is_cicilan').prop('checked', false); $('#wrap_cicilan').addClass('d-none'); $('#tenor').val(''); + $('#stokSisaQty').html(''); setMode('manual'); } @@ -359,33 +364,91 @@ $(function(){ return; } - loadItemBarcodes(item_id); + // loadItemBarcodes(item_id); + loadItemBarcodes( + item_id, + isEditing ? editData.barcode_id : null + ); }); + $('#barcode_select').on('change', function(){ + + let barcode_id = $(this).val(); + + let selected = $(this).find(':selected'); + let qty_sisa = parseFloat(selected.data('qty_sisa')) || 0; + + // console.log("QTY : " + qty_sisa); + if (qty_sisa > 1 ) { + $('#stokSisaQty').html('
Stok : ' + qty_sisa + '

'); + $('#qty').val(''); + $('#wrap_qty_item').removeClass('d-none'); + } + + }); + + // $(document).on('keyup','#qty',function(){ + // hitungTotal(); + // }); + + // function hitungTotal(){ + // let qty_sisa = 1000; + // $('#stokSisaQty').html('
Stok : ' + qty_sisa + '

'); + // } + + + // ================= LOAD BARCODE ITEMS ================= - function loadItemBarcodes(item_id){ + // function loadItemBarcodes(item_id){ + // return $.get("" + item_id, function(res){ + // if(res && res.length){ + // let opt = ''; + // res.forEach(i=>{ + // opt += ``; + // }); + // $('#barcode_select').html(opt); + // $('#wrap_barcode_select').removeClass('d-none'); + + // // set QTY jadi 1 + // let qty = Number($('#qty').val()); + // $('#qty').val(qty + 1); + + // setMode('item'); + // } else { + // $('#wrap_barcode_select').addClass('d-none'); + // // $('#wrap_barcode_select').removeClass('d-none'); + // setMode('manual'); + // } + // }, 'json'); + // } + + function loadItemBarcodes(item_id, selectedBarcode = null){ return $.get("" + item_id, function(res){ if(res && res.length){ - let opt = ''; + let opt = ''; res.forEach(i=>{ - opt += ``; + opt += ` + + `; }); $('#barcode_select').html(opt); + if(selectedBarcode){ + $('#barcode_select').val(selectedBarcode); + } $('#wrap_barcode_select').removeClass('d-none'); - - // set QTY jadi 1 - let qty = Number($('#qty').val()); - $('#qty').val(qty + 1); - + if(!isEditing){ + $('#qty').val(1); + } setMode('item'); - } else { + }else{ $('#wrap_barcode_select').addClass('d-none'); - // $('#wrap_barcode_select').removeClass('d-none'); setMode('manual'); } - }, 'json'); + },'json'); } // ================= AUTO HARGA ================= @@ -398,8 +461,8 @@ $(function(){ // ================= STOK CHECK ================= $('#qty').on('input', function(){ let qty = parseFloat($(this).val()) || 0; - let selected = $('#item_select option:selected'); - let stok = selected.length ? parseFloat(selected.data('stok')) || 0 : 0; + let selected = $('#barcode_select option:selected'); + let stok = selected.length ? parseFloat(selected.data('qty_sisa')) || 0 : 0; if(stok > 0 && qty > stok){ Swal.fire('Warning','Stok tidak cukup! Max: ' + stok,'warning'); @@ -527,25 +590,7 @@ $(function(){ let btnAction = ''; if(v.status == 'draft'){ - let btnEditTable = ''; - if(v.is_cicilan == 0) { - btnEditTable = ` - `; - } btnAction = ` - ${btnEditTable} @@ -614,53 +659,6 @@ $(function(){ modal.show(); }); - // ================= EDIT ================= - $(document).on('click','.btn-editItem', function(){ - action = "edit"; - edit_id = $(this).data('id'); - isEditing = true; - editData = $(this).data(); - - $('#modalTitle').text('Edit Item'); - resetForm(); - - // Set basic fields first - $('#account_id').val(editData.account).trigger('change'); - - // Wait for account change to complete, then set other fields - setTimeout(() => { - if(editData.warehouse){ - $('#warehouse_id').val(editData.warehouse).trigger('change'); - - // gak ada checkbox cicilan - $('#is_cicilan_group').addClass('d-none'); - - // Wait for warehouse items to load - setTimeout(() => { - $('#item_select').val(editData.item_id).trigger('change'); - }, 500); - } else { - $('#nama_item').val(editData.item); - } - - - $('#qty').val(editData.qty); - $('#harga').val(editData.harga); - $('#tanggal_input').val(editData.tanggal_input); - $('#keterangan').val(editData.keterangan); - - }, 500); - - // if(editData.is_cicilan == 1){ - // $('#qty').prop('disabled', true); - // $('#harga').prop('disabled', true); - // $('#is_cicilan').prop('disabled', true); - // $('#tenor').prop('disabled', true); - // } - - modal.show(); - }); - // ================= SAVE ================= // ================= SAVE - FIXED SESUAI STYLE ANDA ================= @@ -693,6 +691,7 @@ $(function(){ qty: parseFloat($('#qty').val()) || 0, harga: parseFloat($('#harga').val()) || 0, keterangan: $('#keterangan').val().trim(), + barcode_id: $('#barcode_select').val() || 0, is_cicilan: $('#is_cicilan').is(':checked') ? 1 : 0, tenor: parseInt($('#tenor').val()) || 0, @@ -757,7 +756,7 @@ $(function(){ btn.prop('disabled', true).html('Menghapus...'); $.get({ - url: "" + id, + url: "" + id + "/" + ``, dataType: 'json' // ✅ FIXED }) .done(function(res){ diff --git a/application/views/items/draft_items.php b/application/views/items/draft_items.php index a73383b..4a27f10 100644 --- a/application/views/items/draft_items.php +++ b/application/views/items/draft_items.php @@ -34,7 +34,7 @@