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',
'
Edit
Delete
@@ -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 @@
Keterangan
-
+
+
- Harga Per Unit
+ Harga
Tanggal
@@ -99,7 +101,8 @@
Keterangan
-
+
+
-
+
Akun Asset (Debit)
@@ -155,14 +158,33 @@
Pilih Barang
-
+
-
Stok Tersedia
-
+
+ Barcode / QR ID
+
+
+
+
+
+
Harga Barang
+
+
+
+ Qty
+
+
+ Total Harga
+
+
Tanggal
+
Keterangan
+
+
+
Masa Manfaat (bulan)
@@ -224,6 +246,10 @@ function formatRupiah(angka){
return angka.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".");
}
+function parseNumber(val){
+ return parseInt((val || '').toString().replace(/\D/g,'')) || 0;
+}
+
function setSelectValue(selector, value){
setTimeout(()=>{
$(selector).val(value).trigger('change');
@@ -269,7 +295,7 @@ function loadItems(){
$.get("= base_url('asset/get_items'); ?>",res=>{
let opt='
-- PILIH BARANG -- ';
res.forEach(i=>{
- opt += `
+ opt += `
${i.kode_detail} - ${i.nama_barang} - ( Stok: ${i.stok})
`;
});
@@ -282,20 +308,81 @@ function hitungSusut(){
let harga = parseInt($('#harga').val().replace(/\D/g,'')) || 0;
let residu = parseInt($('#residu').val().replace(/\D/g,'')) || 0;
let masa = parseInt($('#masa').val()) || 1;
+
+ let maxResidu = harga / 100 * 80;
+
+ if (residu > maxResidu) {
+ residu = maxResidu;
+ $('#residu').val(maxResidu).trigger('input');
+ }
+
let susut = (harga - residu) / masa;
- $('#susut').val(formatRupiah(Math.round(susut)));
+ susut = Math.max(0, susut);
+ $('#susut').val(susut);
}
+// ================= LOAD BARCODE ITEMS =================
+// function loadItemBarcodes(item_id){
+// return $.get("= base_url('items/get_list_barcode_id/'); ?>" + item_id, function(res){
+// if(res && res.length){
+// let opt = '
Pilih Item ';
+// res.forEach(i=>{
+// opt += `
+// ${i.nama_barang} - ${i.barcode} - ${i.serial_number}
+// `;
+// });
+// $('#barcode_select').html(opt);
+// $('#wrap_barcode_select').removeClass('d-none');
+// } else {
+// $('#wrap_barcode_select').addClass('d-none');
+// }
+// }, 'json');
+// }
+
+ function loadItemBarcodes(item_id, selectedBarcode = null){
+ return $.get("= base_url('items/get_list_barcode_id/'); ?>" + item_id, function(res){
+ if(res && res.length){
+ let opt = '
Pilih Barcode ';
+ res.forEach(i=>{
+ opt += `
+
+ ${i.nama_barang} - ${i.barcode} - ${i.serial_number}
+
+ `;
+ });
+ $('#barcode_select').html(opt);
+ if(selectedBarcode){
+ $('#barcode_select').val(selectedBarcode);
+ }
+ $('#wrap_barcode_select').removeClass('d-none');
+
+ // setMode('item');
+ }else{
+ $('#wrap_barcode_select').addClass('d-none');
+ // setMode('manual');
+ }
+ },'json');
+ }
+
+
$('#masa, #residu, #harga').on('keyup change',hitungSusut);
-// ================= HITUNG SUSUT STOCK =================
-$('#s_masa, #s_residu').on('keyup change',function(){
- let harga = parseInt($('#s_harga').val()) || 0;
- let residu = parseInt($('#s_residu').val().replace(/\D/g,'')) || 0;
+function hitungSusutStock(){
+ let trackingType = ($('#s_item option:selected').data('type') || '').toString().trim().toUpperCase();
+ let hargaDasar = trackingType === 'QTY' ? parseNumber($('#s_total_harga').val()) : parseNumber($('#s_harga').val());
+ let residu = parseNumber($('#s_residu').val());
let masa = parseInt($('#s_masa').val()) || 1;
- let susut = (harga - residu) / masa;
- $('#s_susut').val(formatRupiah(Math.round(susut)));
-});
+ let maxResidu = hargaDasar / 100 * 80;
+
+ if (residu > maxResidu) {
+ residu = maxResidu;
+ $('#s_residu').val(maxResidu).trigger('input');
+ }
+ let susut = (hargaDasar - residu) / masa;
+ susut = Math.max(0, susut);
+ $('#s_susut').val(susut);
+}
+$('#s_masa, #s_residu').on('keyup change', hitungSusutStock);
// ================= TAMBAH ASSET MANUAL =================
$('.btn-add-asset').click(function(){
@@ -303,26 +390,77 @@ $('.btn-add-asset').click(function(){
$('#modalTitleAsset').text('Tambah Asset');
$('#modalAsset input[type="text"], #modalAsset input[type="date"], #modalAsset input[type="number"], #modalAsset select').val('');
$('#id_asset').val('');
+ $('#stokSisaQty').html('');
loadKas(); loadAsset(); loadLokasiAsset();
$('#modalAsset').modal('show');
});
+
+
+
// ================= DETAIL & HISTORY =================
$(document).on('click','.btn-detail',function(){
let id = $(this).data('id');
$.get("= base_url('asset/detail/'); ?>"+id, function(asset){
$('#detailContent').html(`
-
- Kode Asset : ${asset.kode_asset || '-'}
- Nama Asset : ${asset.nama_asset}
- Lokasi : ${asset.nama_lokasi || '-'}
- Harga/Unit : Rp ${formatRupiah(asset.harga_per_unit)}
- Nilai Buku : Rp ${formatRupiah(asset.nilai_buku)}
- Masa Manfaat : ${asset.masa_manfaat} bulan
- Sumber : ${asset.sumber}
- Keterangan : ${asset.keterangan || '-'}
-
+
+
+
+
+
+ Kode Asset
+ : ${asset.kode_asset || '-'}
+
+
+ Nama Asset
+ : ${asset.nama_asset}
+
+
+ Lokasi
+ : ${asset.nama_lokasi || '-'}
+
+
+ Harga/Unit
+ : Rp ${formatRupiah(asset.harga_per_unit)}
+
+
+ Qty
+ : ${asset.qty}
+
+
+ Nilai Perolehan
+ : Rp ${formatRupiah(asset.nilai_perolehan)}
+
+
+
+
+
+
+
+ Akumulasi Penyusutan
+ : Rp ${formatRupiah(asset.akumulasi_penyusutan || 0)}
+
+
+ Nilai Buku
+ : Rp ${formatRupiah(asset.nilai_buku)}
+
+
+ Masa Manfaat
+ : ${asset.masa_manfaat} bulan
+
+
+ Sumber
+ : ${asset.sumber}
+
+
+ Keterangan
+ : ${asset.keterangan || '-'}
+
+
+
+
+
`);
// Load history
@@ -336,7 +474,7 @@ $(document).on('click','.btn-detail',function(){
h.tipe=='penambahan' ? 'bg-info' :
h.tipe=='pengurangan' ? 'bg-warning' : 'bg-secondary';
rows += `
- ${h.created_at}
+ ${h.tanggal_dibuat}
${h.tipe}
Rp ${formatRupiah(h.nilai || 0)}
${h.keterangan}
@@ -465,35 +603,157 @@ $('#btnSave').click(function(){
});
// ================= FROM STOCK =================
-$('.btn-from-stock').click(function(){
+function resetStockForm(){
$('#modalAssetStock input[type="text"], #modalAssetStock input[type="date"], #modalAssetStock input[type="number"], #modalAssetStock select').val('');
- $('#s_item_id, #s_harga').val('');
+ $('#s_item_id, #s_harga, #s_total_harga, #s_susut, #s_keterangan').val('');
+ $('#s_qty').val(1);
+ $('#stokSisaQty').html('');
+ $('#wrap_qty').addClass('d-none');
+ $('#wrap_barcode_select').removeClass('d-none');
+ $('#barcode_select').html('Loading... ');
+}
+
+$('.btn-from-stock').click(function(){
+ resetStockForm();
loadItems();
loadAsset();
loadLokasiAsset();
$('#modalAssetStock').modal('show');
});
-// ================= PILIH ITEM STOCK =================
-$(document).on('change','#s_item',function(){
+
+$('#barcode_select').on('change', function(){
+ let selectedItem = $('#s_item option:selected');
+ let type = (selectedItem.data('type') || '').toString().trim().toUpperCase();
let selected = $(this).find(':selected');
- let harga = selected.data('harga') || 0;
- let stok = selected.data('stok') || 0;
- $('#s_item_id').val($(this).val());
+ let qty_sisa = parseFloat(selected.data('qty_sisa')) || 0;
+
+ if($(this).val()){
+ $('#stokSisaQty').html('Stok : ' + qty_sisa + ' ');
+ }else{
+ $('#stokSisaQty').html('');
+ }
+
+ // perlakuan qty setelah barcode dipilih, berdasarkan tracking_type
+ if(type === 'UNIT'){
+ $('#stokSisaQty').html('');
+ $('#wrap_qty').addClass('d-none');
+ $('#s_qty').removeAttr('max').val(1);
+ $('#s_total_harga').val(parseNumber($('#s_harga').val()));
+ hitungSusutStock();
+ }else{
+ $('#wrap_qty').removeClass('d-none');
+ $('#s_qty').attr('min', 1).attr('max', qty_sisa > 0 ? qty_sisa : 1).val('');
+ $('#s_total_harga').val('');
+ }
+});
+
+$('#s_item').on('change', function(){
+ let selected = $(this).find(':selected');
+ let item_id = selected.data('id');
+ let type = selected.data('type');
+ let harga = parseInt(selected.data('harga')) || 0;
+ let stok = parseFloat(selected.data('stok')) || 0;
+
+ $('#s_item_id').val(item_id || '');
$('#s_harga').val(harga);
- $('#s_stok').val(stok);
+ $('#s_total_harga').val(harga);
+ $('#stokSisaQty').html('');
+ $('#s_qty').val(1).removeAttr('max');
+
+ $('#wrap_qty').addClass('d-none');
+ $('#wrap_barcode_select').addClass('d-none');
+ $('#barcode_select').html('Pilih Barcode ');
+
+ if(!item_id){
+ return;
+ }
+
+ let normalizedType = (type || '').toString().trim().toUpperCase();
+ // barcode tetap harus muncul untuk ITEM maupun QTY
+ loadItemBarcodes(item_id, null).done(function(res){
+ if(res && res.length){
+ $('#wrap_barcode_select').removeClass('d-none');
+ }else{
+ $('#wrap_barcode_select').addClass('d-none');
+ $('#barcode_select').html('Barcode tidak tersedia ');
+ }
+
+ // sebelum pilih barcode: qty & total belum ditentukan
+ $('#wrap_qty').addClass('d-none');
+ $('#s_qty').removeAttr('max').val('');
+ $('#s_total_harga').val('');
+ }).fail(function(){
+ $('#wrap_barcode_select').addClass('d-none');
+ $('#barcode_select').html('Gagal load barcode ');
+ $('#wrap_qty').addClass('d-none');
+ $('#s_qty').removeAttr('max').val('');
+ $('#s_total_harga').val('');
+ });
+});
+
+$('#s_qty').on('keyup change',function(){
+ let selectedItem = $('#s_item option:selected');
+ let type = (selectedItem.data('type') || '').toString().trim().toUpperCase();
+ let qtyRaw = $('#s_qty').val();
+ let qty = parseInt(qtyRaw);
+ let harga = parseNumber($('#s_harga').val());
+ let max = parseFloat($('#s_qty').attr('max')) || 0;
+
+ if(type === 'UNIT'){
+ $('#wrap_qty').addClass('d-none');
+ $('#s_qty').val(1);
+ $('#s_total_harga').val(harga);
+ return;
+ }
+
+ if(!qtyRaw || isNaN(qty) || qty < 1){
+ $('#s_total_harga').val('');
+ return;
+ }
+
+ if(max > 0 && qty > max){
+ qty = max;
+ Swal.fire('Warning','Stok tidak cukup! Max: ' + max,'warning');
+ }
+
+ let total = harga * qty;
+ $('#s_qty').val(qty);
+ $('#s_total_harga').val(total);
+ hitungSusutStock();
});
// ================= SAVE FROM STOCK =================
$('#btnSaveStock').click(function(){
let btn = $(this);
+ let trackingType = ($('#s_item option:selected').data('type') || '').toString().trim().toUpperCase();
+ let maxQty = parseFloat($('#s_qty').attr('max')) || 0;
+ let qtyRaw = $('#s_qty').val();
+ let qty = parseInt(qtyRaw);
+
+ if(trackingType === 'UNIT'){
+ qty = 1;
+ }else{
+ if(!qtyRaw || isNaN(qty) || qty < 1){
+ Swal.fire('Error','Qty wajib diisi untuk item tracking QTY','error');
+ return;
+ }
+ if(maxQty > 0 && qty > maxQty){
+ Swal.fire('Error','Qty melebihi stok tersedia','error');
+ return;
+ }
+ }
+
let data = {
item_id: $('#s_item').val(),
s_tanggal: $('#s_tanggal').val(),
s_masa: $('#s_masa').val(),
s_residu: $('#s_residu').val(),
s_lokasi_asset: $('#s_lokasi_asset').val(),
- account_asset: $('#s_account_asset').val()
+ account_asset: $('#s_account_asset').val(),
+ s_keterangan: $('#s_keterangan').val(),
+ barcode_id: $('#barcode_select').val(),
+ qty: qty
};
// Validasi
@@ -502,6 +762,15 @@ $('#btnSaveStock').click(function(){
return;
}
+ if(!data.barcode_id){
+ Swal.fire('Error','Barcode wajib dipilih','error');
+ return;
+ }
+
+ if(trackingType === 'UNIT'){
+ data.qty = 1;
+ }
+
btn.prop('disabled', true).html('Menyimpan...');
$.post("= base_url('asset/save_from_stock'); ?>",data,function(res){
@@ -523,5 +792,6 @@ $('#btnSaveStock').click(function(){
});
});
+
});
\ No newline at end of file
diff --git a/application/views/invoices/detail.php b/application/views/invoices/detail.php
index 0f10541..7f28554 100644
--- a/application/views/invoices/detail.php
+++ b/application/views/invoices/detail.php
@@ -122,10 +122,12 @@
- Barcode ID
+ Barcode / QR ID
+
+
Qty
@@ -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('
Pilih Gudang ');
$('#item_select').html('
Pilih Item ');
@@ -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("= base_url('items/get_list_barcode_id/'); ?>" + item_id, function(res){
+ // if(res && res.length){
+ // let opt = '
Pilih Item ';
+ // res.forEach(i=>{
+ // opt += `
+ // ${i.nama_barang} - ${i.barcode} - ${i.serial_number}
+ // `;
+ // });
+ // $('#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("= base_url('items/get_list_barcode_id/'); ?>" + item_id, function(res){
if(res && res.length){
- let opt = '
Pilih Item ';
+ let opt = '
Pilih Barcode ';
res.forEach(i=>{
- opt += `
- ${i.nama_barang} - ${i.barcode} - ${i.serial_number}
- `;
+ opt += `
+
+ ${i.nama_barang} - ${i.barcode} - ${i.serial_number}
+
+ `;
});
$('#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 = `
-
- Edit
- `;
- }
btnAction = `
- ${btnEditTable}
Hapus
@@ -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: "= base_url('invoices/delete_item/'); ?>" + id,
+ url: "= base_url('invoices/delete_item/'); ?>" + id + "/" + `= $no_invoice ?>`,
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 @@
@@ -49,6 +52,25 @@
Limit Stok
+
Tracking Type
+
+ UNIT
+ QTY
+
+
+
Need Serial Number
+
+ Tidak
+ Ya
+
+
+
Unit
+
+ PCS
+ Meter
+ Lembar
+
+