Compare commits

..

17 Commits

Author SHA1 Message Date
sean 0d1f83872c perbaikan di invoices 2026-07-30 09:49:00 +07:00
sean 4c2fefe9c3 tambah kolom server di pdf barang bawaan 2026-07-29 08:01:24 +07:00
sean 7d90548717 update form barang 2026-07-27 08:32:05 +07:00
sean 0a16246138 log barang teknisi satu halaman 2026-07-24 19:02:21 +07:00
sean abbabf4af8 update log barang teknisi 2026-07-24 15:41:19 +07:00
sean f145b9e2ec Merge branch 'main' of https://git.radiq.my.id/admin/accounting_dev 2026-07-24 10:33:29 +07:00
sean ea969beedc update barcode yg baru 2026-07-24 10:32:52 +07:00
Wian Drs f715a8d637 update invoice barcode id 2026-07-24 00:01:55 +07:00
sean aa326838fc form barang bawaan teknisi dan fix bug draft item 2026-07-22 21:05:37 +07:00
sean 8a56e2f485 Update history peralatan di bawah teknisi 2026-07-18 11:44:27 +07:00
Prod-Panel c7ec369234 Update Controller Items karena ada bug 2026-07-12 16:55:48 +00:00
Wian Drs 81e35f56a0 Merge branch 'main' of https://git.radiq.my.id/admin/accounting_dev 2026-07-12 22:12:44 +07:00
Wian Drs 3113d77242 Perubahan barcode dan inventory 2026-07-12 22:10:02 +07:00
Prod-Panel 776f22ae24 Perubahan invoice di server 2026-07-12 15:09:52 +00:00
Wian Drs 3ac6316da4 Barcode Scan + Barcode Print 2026-06-30 11:16:31 +07:00
sean be5a02150e Merge branch 'main' of github.com:yandrs/accounting 2026-06-15 14:43:53 +07:00
sean 36ad02a222 Penambahan tombol PDF di Bukubesar, Neraca, Laba rugi, Neraca saldo, dan Data Barang
Format PDF tersimpan di library semua
2026-06-13 16:03:15 +07:00
43 changed files with 9567 additions and 838 deletions
+5
View File
@@ -0,0 +1,5 @@
# CodeGraph data files — local to each machine, not for committing.
# Ignore everything in .codegraph/ except this file itself, so transient
# files (the database, daemon.pid, sockets, logs) never show up in git.
*
!.gitignore
+8
View File
@@ -2,8 +2,11 @@
application/config/config.php
application/config/database.php
application/controllers/Dbcompare.php
.htaccess
uploads/temp_qr/*
application/cache/*
!application/cache/index.html
@@ -32,3 +35,8 @@ user_guide_src/cilexer/pycilexer.egg-info/*
*.sublime-project
/tests/tests/
/tests/results/
/dev-docs/
/ai-rules/
*.omo/
@@ -0,0 +1,10 @@
{
"sessionID": "ses_068c091cfffeSZ25RoesHkRZHA",
"updatedAt": "2026-07-25T03:09:04.625Z",
"sources": {
"background-task": {
"state": "idle",
"updatedAt": "2026-07-25T03:09:04.625Z"
}
}
}
@@ -0,0 +1,10 @@
{
"sessionID": "ses_0690d5681fferVVn0tDzMM9Ow7",
"updatedAt": "2026-07-25T08:09:46.929Z",
"sources": {
"background-task": {
"state": "idle",
"updatedAt": "2026-07-25T08:09:46.929Z"
}
}
}
+19
View File
@@ -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
- [x] Refactor logic depreciation_asset agar memproses multi-bulan (catch-up)
- [x] Terapkan loop per bulan dari bulan setelah tanggal patokan sampai bulan berjalan
- [x] Pertahankan idempotent check per bulan berdasarkan asset_mutations
- [x] Generate mutasi + jurnal per bulan yang eligible
- [x] Update nilai buku & akumulasi penyusutan asset setelah proses loop
- [x] Tambahkan detail response per bulan (processed/skipped reason)
- [x] Validasi syntax PHP untuk Cronjob.php
+7
View File
@@ -56,3 +56,10 @@ $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';
// teknisi log barang
$route['teknisi/log_barang'] = 'teknisi/log_barang';
+137 -22
View File
@@ -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(){
@@ -18,10 +22,11 @@ class Asset extends CI_Controller {
// ================= GET DATA =================
public function get_data(){
$data = $this->db
->select('assets.*, lokasi_asset.nama as nama_lokasi')
->select('assets.*, lokasi_asset.nama as nama_lokasi, item_barcodes.barcode as barcode, item_barcodes.id as barcode_id')
->from('assets')
->join('lokasi_asset', 'lokasi_asset.id = assets.lokasi_asset_id', 'left')
->order_by('assets.id','DESC')
->join('item_barcodes', 'item_barcodes.id = assets.barcode_id', 'left')
->order_by('assets.tanggal_perolehan','DESC')
->get()
->result();
@@ -33,8 +38,9 @@ class Asset extends CI_Controller {
$no++,
$d->tanggal_perolehan,
$d->kode_asset ?? '-',
$d->nama_asset,
$d->nama_asset . ($d->barcode_id ? '<br><small class="text-muted"><i>'.$d->barcode.'</i></small>' : ''),
$d->nama_lokasi ?? '-',
$d->keterangan ?? '-',
number_format($d->nilai_perolehan),
$d->masa_manfaat . ' bln',
number_format($d->penyusutan_per_bulan),
@@ -101,11 +107,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 +149,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 +171,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,21 +185,27 @@ 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();
// INSERT ASSET
$this->db->insert('assets',[
'item_id' => $item_id,
'barcode_id' => $barcode_id,
'kode_asset' => 'AST-'.time(),
'nama_asset' => $nama_asset,
'harga_per_unit' => $harga,
@@ -192,10 +218,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 +284,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 +294,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 +335,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 +373,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 +403,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);
}
+320 -4
View File
@@ -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 {
}
}
}
// =====================================================
// 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()
]);
}
}
}
+2
View File
@@ -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 =================
+26
View File
@@ -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'
+138
View File
@@ -0,0 +1,138 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Generatebuku extends CI_Controller
{
public function __construct()
{
parent::__construct();
$this->load->model('DynamicModel', 'dm');
}
// ----------------------------------------------------------------
// PDF GENERATE — BUKU BESAR
// ----------------------------------------------------------------
public function generatepdf($account_id, $start, $end)
{
if ($this->session->userdata('role') != 'Admin') {
show_error('Akses ditolak', 403);
}
if (ob_get_length()) {
ob_end_clean();
}
// =============================================================
// AMBIL INFO AKUN
// =============================================================
$akun = null;
if (!empty($account_id)) {
$akun = $this->db->get_where('accounts', ['id' => (int) $account_id])->row();
}
// =============================================================
// QUERY DATA
// =============================================================
$this->db->select('
journals.tanggal,
journals.no_ref,
journals.keterangan,
journal_details.debit,
journal_details.kredit,
accounts.tipe
');
$this->db->from('journal_details');
$this->db->join('journals', 'journals.id = journal_details.journal_id');
$this->db->join('accounts', 'accounts.id = journal_details.account_id');
if (!empty($account_id)) {
$this->db->where('journal_details.account_id', (int) $account_id);
}
if (!empty($start) && !empty($end)) {
$this->db->where('journals.tanggal >=', $start);
$this->db->where('journals.tanggal <=', $end);
}
$this->db->group_start();
$this->db->where('journal_details.debit >', 0);
$this->db->or_where('journal_details.kredit >', 0);
$this->db->group_end();
$this->db->order_by('journals.tanggal', 'ASC');
$rows = $this->db->get()->result();
// =============================================================
// HITUNG SALDO BERJALAN
// =============================================================
$saldo = 0;
$result = [];
foreach ($rows as $r) {
if (in_array($r->tipe, ['asset', 'expense'])) {
$saldo += ($r->debit - $r->kredit);
} else {
$saldo += ($r->kredit - $r->debit);
}
$result[] = [
'tanggal' => $r->tanggal,
'no_ref' => $r->no_ref,
'keterangan' => $r->keterangan,
'debit' => $r->debit,
'kredit' => $r->kredit,
'saldo' => $saldo,
];
}
$total_debit = array_sum(array_column($result, 'debit'));
$total_kredit = array_sum(array_column($result, 'kredit'));
// =============================================================
// DATA PERUSAHAAN
// =============================================================
$company = $this->db->get('pengaturan')->row();
// =============================================================
// BUILD STRING INFO LAPORAN
// =============================================================
$namaAkun = $akun
? $akun->kode_akun . ' - ' . $akun->nama_akun
: 'Semua Akun';
$periodeStr = (!empty($start) && !empty($end))
? 'Periode : ' . date('d-m-Y', strtotime($start)) . ' s/d ' . date('d-m-Y', strtotime($end))
: 'Periode : Semua Tanggal';
// =============================================================
// LOAD LIBRARY PDF
// =============================================================
require_once(APPPATH . 'third_party/fpdf/fpdf.php');
require_once(APPPATH . 'libraries/PDF_BukuBesar.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'
);
$pdf->setInfoLaporan($namaAkun, $periodeStr);
$pdf->AddPage('P', 'A4');
$pdf->BukuBesarTable($result);
$pdf->BukuBesarTotal($total_debit, $total_kredit, $saldo);
$pdf->Pembuat('Tito Ngudiana', 'Finance Department');
// =============================================================
// OUTPUT
// =============================================================
$filename = 'Buku_Besar_' . date('YmdHis') . '.pdf';
$pdf->Output('D', $filename);
exit;
}
}
+68
View File
@@ -0,0 +1,68 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Generateitems extends CI_Controller {
public function __construct()
{
parent::__construct();
$this->load->model('DynamicModel', 'dm');
}
public function generatepdf()
{
if(ob_get_length()){
ob_end_clean();
}
$this->db->select('
items.id,
items.nama_barang,
items.kode_detail,
items.harga_beli,
items.harga_jual,
items.created_at,
items.tanggal_pembelian,
items.stok,
(
SELECT GROUP_CONCAT(DISTINCT w.nama SEPARATOR ", ")
FROM stock_logs sl
LEFT JOIN warehouses w ON sl.warehouse_id = w.id
WHERE sl.item_id = items.id
) as gudang
');
$this->db->where('items.status','active');
$this->db->where('items.stok >', 0); // <-- stok 0 agar tidak muncul
$this->db->order_by('(items.stok = 0)','ASC',false);
$this->db->order_by('items.tanggal_pembelian','ASC');
$this->db->order_by('items.kode_detail','ASC');
$data = $this->db->get('items')->result();
$company = $this->db->get('pengaturan')->row_array();
require_once(APPPATH . 'third_party/fpdf/fpdf.php');
require_once(APPPATH. 'libraries/PDF_Items.php');
$pdf = new PDF();
$pdf->setHeaderData(
$company['nama_perusahaan'],
$company['alamat_perusahaan'],
$company['telepon_perusahaan'],
FCPATH.'uploads/img/logo-ljn.png'
);
$pdf->AddPage('P', 'A4');
$pdf->ItemsTable($data);
$pdf->TotalBarang(count($data));
// $pdf->Pembuat($company['nama_bendahara']);
$pdf->Output('I','Data_Barang_'.date('YmdHis').'.pdf');
}
}
@@ -0,0 +1,197 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Generatelabarugi extends CI_Controller {
public function __construct()
{
parent::__construct();
$this->load->model('DynamicModel', 'dm');
if (!$this->session->userdata('logged_in')) {
redirect('auth');
}
}
public function index()
{
if($this->session->userdata('role') != 'Admin'){
show_error('Akses ditolak', 403);
}
$data = ["active_menu" => "laba_rugi"];
$this->load->view('partials/header', $data);
$this->load->view('laporan/laba_rugi');
$this->load->view('partials/footer');
}
// =========================
// AMBIL DATA LABA RUGI (DIPAKAI BERSAMA OLEH get_data() & generatepdf())
// =========================
private function get_labarugi_data($tanggal_dari = null, $tanggal_sampai = null)
{
$this->db->select('
a.id,
a.kode_akun,
a.nama_akun,
a.tipe,
COALESCE(SUM(jd.debit),0) as debit,
COALESCE(SUM(jd.kredit),0) as kredit
');
$this->db->from('accounts a');
// ================= JOIN
$this->db->join('journal_details jd','a.id = jd.account_id','LEFT');
$this->db->join('journals j','j.id = jd.journal_id','LEFT');
// ================= FILTER AKUN
$this->db->where('a.kategori','laba_rugi');
$this->db->where('a.is_active',1);
// ================= FILTER TANGGAL (AMAN)
if($tanggal_dari){
$this->db->where('(j.tanggal >= "'.$tanggal_dari.'" OR j.tanggal IS NULL)');
}
if($tanggal_sampai){
$this->db->where('(j.tanggal <= "'.$tanggal_sampai.'" OR j.tanggal IS NULL)');
}
$this->db->group_by('a.id');
$this->db->order_by('a.kode_akun','ASC');
$rows = $this->db->get()->result();
$pendapatan = [];
$beban = [];
$revenue = 0;
$expense = 0;
foreach ($rows as $r) {
if ($r->tipe == 'revenue') {
$saldo = $r->kredit - $r->debit;
$revenue += $saldo;
$pendapatan[] = [
'kode' => $r->kode_akun,
'nama' => $r->nama_akun,
'saldo' => $saldo
];
}
if ($r->tipe == 'expense') {
$saldo = $r->debit - $r->kredit;
$expense += $saldo;
$beban[] = [
'kode' => $r->kode_akun,
'nama' => $r->nama_akun,
'saldo' => $saldo
];
}
}
return [
'data' => $rows,
'pendapatan' => $pendapatan,
'beban' => $beban,
'revenue' => $revenue,
'expense' => $expense,
'total_pendapatan'=> $revenue,
'total_beban' => $expense,
'laba' => $revenue - $expense
];
}
// =========================
// GET DATA LABA RUGI (JSON, untuk AJAX/datatable)
// =========================
public function get_data()
{
$tanggal_dari = $this->input->get('tanggal_dari');
$tanggal_sampai = $this->input->get('tanggal_sampai');
$result = $this->get_labarugi_data($tanggal_dari, $tanggal_sampai);
echo json_encode([
'data' => $result['data'],
'revenue' => $result['revenue'],
'expense' => $result['expense'],
'laba' => $result['laba']
]);
}
// =========================
// PDF GENERATE — LABA RUGI
// =========================
public function generatepdf($start = null, $end = null)
{
if ($this->session->userdata('role') != 'Admin') {
show_error('Akses ditolak', 403);
}
if (ob_get_length()) {
ob_end_clean();
}
// Normalisasi parameter URL (0000-00-00 dianggap kosong)
$tanggal_dari = (!empty($start) && $start != '0000-00-00') ? $start : null;
$tanggal_sampai = (!empty($end) && $end != '0000-00-00') ? $end : null;
// =============================================================
// AMBIL DATA
// =============================================================
$labarugi = $this->get_labarugi_data($tanggal_dari, $tanggal_sampai);
// =============================================================
// DATA PERUSAHAAN
// =============================================================
$company = $this->db->get('pengaturan')->row();
// =============================================================
// BUILD STRING PERIODE
// =============================================================
$periodeStr = (!empty($tanggal_dari) && !empty($tanggal_sampai))
? 'Periode : ' . date('d-m-Y', strtotime($tanggal_dari))
. ' s/d ' . date('d-m-Y', strtotime($tanggal_sampai))
: ((!empty($tanggal_sampai))
? 'Per Tanggal : ' . date('d-m-Y', strtotime($tanggal_sampai))
: 'Per Tanggal : ' . date('d-m-Y'));
// =============================================================
// LOAD LIBRARY PDF
// =============================================================
require_once(APPPATH . 'third_party/fpdf/fpdf.php');
require_once(APPPATH . 'libraries/PDF_Labarugi.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'
);
$pdf->setInfoLaporan($periodeStr);
$pdf->AddPage('P', 'A4');
$pdf->LabaRugiTable(
$labarugi['pendapatan'],
$labarugi['beban'],
$labarugi['total_pendapatan'],
$labarugi['total_beban']
);
$pdf->LabaRugiRingkasan($labarugi['laba']);
$pdf->Pembuat('Tito Ngudiana', 'Finance Department');
// =============================================================
// OUTPUT
// =============================================================
$filename = 'Laba_Rugi_' . date('YmdHis') . '.pdf';
$pdf->Output('D', $filename);
exit;
}
}
+252
View File
@@ -0,0 +1,252 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Generateneraca extends CI_Controller {
public function __construct()
{
parent::__construct();
$this->load->model('DynamicModel', 'dm');
if (!$this->session->userdata('logged_in')) {
redirect('auth');
}
}
public function index()
{
if($this->session->userdata('role') != 'Admin'){
show_error('Akses ditolak', 403);
}
$data = ["active_menu" => "neraca"];
$this->load->view('partials/header', $data);
$this->load->view('laporan/neraca');
$this->load->view('partials/footer');
}
// =========================
// LABA BERJALAN (PAKAI TANGGAL JOURNAL)
// =========================
private function get_laba_berjalan($tanggal_dari = null, $tanggal_sampai = null)
{
$this->db->select('
accounts.tipe,
COALESCE(SUM(jd.debit),0) as debit,
COALESCE(SUM(jd.kredit),0) as kredit
');
$this->db->from('accounts');
$this->db->join('journal_details jd','jd.account_id = accounts.id','LEFT');
$this->db->join('journals j','j.id = jd.journal_id','LEFT');
$this->db->where_in('accounts.tipe',['revenue','expense']);
// ================= FILTER TANGGAL
if($tanggal_dari){
$this->db->where('(j.tanggal >= "'.$tanggal_dari.'" OR j.tanggal IS NULL)');
}
if($tanggal_sampai){
$this->db->where('(j.tanggal <= "'.$tanggal_sampai.'" OR j.tanggal IS NULL)');
}
$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 (DIPAKAI BERSAMA OLEH get_data() & generatepdf())
// =========================
private function get_neraca_data($tanggal_dari = null, $tanggal_sampai = null)
{
$this->db->select('
a.id,
a.kode_akun,
a.nama_akun,
a.tipe,
a.posisi,
a.is_kontra,
COALESCE(SUM(jd.debit),0) as debit,
COALESCE(SUM(jd.kredit),0) as kredit
');
$this->db->from('accounts a');
$this->db->join('journal_details jd','jd.account_id = a.id','LEFT');
$this->db->join('journals j','j.id = jd.journal_id','LEFT');
$this->db->where('a.kategori','neraca');
// ================= FILTER TANGGAL
if($tanggal_dari){
$this->db->where('(j.tanggal >= "'.$tanggal_dari.'" OR j.tanggal IS NULL)');
}
if($tanggal_sampai){
$this->db->where('(j.tanggal <= "'.$tanggal_sampai.'" OR j.tanggal IS NULL)');
}
$this->db->group_by('a.id');
$this->db->order_by('a.kode_akun','ASC');
$rows = $this->db->get()->result();
$activa = [];
$pasiva = [];
$total_activa = 0;
$total_pasiva = 0;
// ================= LABA BERJALAN
$laba = $this->get_laba_berjalan($tanggal_dari, $tanggal_sampai);
foreach ($rows as $r) {
// ================= SALDO NORMAL
if ($r->posisi == 'debit') {
$saldo = $r->debit - $r->kredit;
} else {
$saldo = $r->kredit - $r->debit;
}
// ================= KONTRA ACCOUNT
if ($r->is_kontra == 1) {
$saldo = -$saldo;
}
// ================= LABA BERJALAN
if ($r->kode_akun == '302') {
$saldo = $laba;
}
// ================= ACTIVA
if ($r->tipe == 'asset') {
$activa[] = [
'kode' => $r->kode_akun,
'nama' => $r->nama_akun,
'saldo' => $saldo
];
$total_activa += $saldo;
}
// ================= PASIVA
if (in_array($r->tipe, ['liability','equity'])) {
$pasiva[] = [
'kode' => $r->kode_akun,
'nama' => $r->nama_akun,
'saldo' => $saldo
];
$total_pasiva += $saldo;
}
}
return [
'activa' => $activa,
'pasiva' => $pasiva,
'total_activa' => $total_activa,
'total_pasiva' => $total_pasiva,
'laba' => $laba,
'selisih' => $total_activa - $total_pasiva
];
}
// =========================
// GET DATA NERACA (JSON, untuk AJAX/datatable)
// =========================
public function get_data()
{
$tanggal_dari = $this->input->get('tanggal_dari');
$tanggal_sampai = $this->input->get('tanggal_sampai');
$data = $this->get_neraca_data($tanggal_dari, $tanggal_sampai);
echo json_encode($data);
}
// =========================
// PDF GENERATE — NERACA
// =========================
public function generatepdf($start = null, $end = null)
{
if ($this->session->userdata('role') != 'Admin') {
show_error('Akses ditolak', 403);
}
if (ob_get_length()) {
ob_end_clean();
}
// Normalisasi parameter URL (0000-00-00 dianggap kosong)
$tanggal_dari = (!empty($start) && $start != '0000-00-00') ? $start : null;
$tanggal_sampai = (!empty($end) && $end != '0000-00-00') ? $end : null;
// =============================================================
// AMBIL DATA
// =============================================================
$neraca = $this->get_neraca_data($tanggal_dari, $tanggal_sampai);
// =============================================================
// DATA PERUSAHAAN
// =============================================================
$company = $this->db->get('pengaturan')->row();
// =============================================================
// BUILD STRING PERIODE
// =============================================================
$periodeStr = (!empty($tanggal_dari) && !empty($tanggal_sampai))
? 'Per Tanggal : ' . date('d-m-Y', strtotime($tanggal_sampai))
. ' (Sejak ' . date('d-m-Y', strtotime($tanggal_dari)) . ')'
: ((!empty($tanggal_sampai))
? 'Per Tanggal : ' . date('d-m-Y', strtotime($tanggal_sampai))
: 'Per Tanggal : ' . date('d-m-Y'));
// =============================================================
// LOAD LIBRARY PDF
// =============================================================
require_once(APPPATH . 'third_party/fpdf/fpdf.php');
require_once(APPPATH . 'libraries/PDF_Neraca.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'
);
$pdf->setInfoLaporan($periodeStr);
$pdf->AddPage('P', 'A4');
$pdf->NeracaTable(
$neraca['activa'],
$neraca['pasiva'],
$neraca['total_activa'],
$neraca['total_pasiva']
);
$pdf->NeracaRingkasan($neraca['laba'], $neraca['selisih']);
$pdf->Pembuat('Tito Ngudiana', 'Finance Department');
// =============================================================
// OUTPUT
// =============================================================
$filename = 'Neraca_' . date('YmdHis') . '.pdf';
$pdf->Output('D', $filename);
exit;
}
}
@@ -0,0 +1,188 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Generateneracasaldo extends CI_Controller {
public function __construct()
{
parent::__construct();
$this->load->model('DynamicModel', 'dm');
if (!$this->session->userdata('logged_in')) {
redirect('auth');
}
}
public function index()
{
if($this->session->userdata('role') != 'Admin'){
show_error('Akses ditolak', 403);
}
$data = ["active_menu" => "neraca_saldo"];
$this->load->view('partials/header', $data);
$this->load->view('laporan/neraca_saldo');
$this->load->view('partials/footer');
}
// =========================
// AMBIL DATA NERACA SALDO
// DIPAKAI OLEH AJAX & PDF
// =========================
private function get_neracasaldo_data()
{
$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,
COALESCE(SUM(journal_details.debit),0) AS debit,
COALESCE(SUM(journal_details.kredit),0) AS kredit
");
$this->db->from('accounts');
// ======================================
// FILTER PERIODE
// ======================================
if (!empty($tanggal_awal) && !empty($tanggal_akhir)) {
$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
);
} else {
$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();
$total_debit = 0;
$total_kredit = 0;
foreach($rows as $row){
$row->debit = (float)$row->debit;
$row->kredit = (float)$row->kredit;
$total_debit += $row->debit;
$total_kredit += $row->kredit;
}
return [
'tanggal_awal' => $tanggal_awal,
'tanggal_akhir' => $tanggal_akhir,
'rows' => $rows,
'total_debit' => $total_debit,
'total_kredit' => $total_kredit
];
}
public function get_data()
{
$result = $this->get_neracasaldo_data();
echo json_encode($result['rows']);
}
public function generatepdf()
{
if ($this->session->userdata('role') != 'Admin') {
show_error('Akses ditolak',403);
}
if (ob_get_length()) {
ob_end_clean();
}
// ==========================================
// DATA
// ==========================================
$neracasaldo = $this->get_neracasaldo_data();
// ==========================================
// PERUSAHAAN
// ==========================================
$company = $this->db->get('pengaturan')->row();
// ==========================================
// PERIODE
// ==========================================
if(!empty($neracasaldo['tanggal_awal']) && !empty($neracasaldo['tanggal_akhir'])){
$periodeStr = 'Periode : '
. date('d-m-Y',strtotime($neracasaldo['tanggal_awal']))
.' s/d '
. date('d-m-Y',strtotime($neracasaldo['tanggal_akhir']));
}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'
);
$pdf->setInfoLaporan($periodeStr);
$pdf->AddPage();
$pdf->NeracaSaldoTable(
$neracasaldo['rows'],
$neracasaldo['total_debit'],
$neracasaldo['total_kredit']
);
$pdf->NeracaSaldoRingkasan(
$neracasaldo['total_debit'],
$neracasaldo['total_kredit']
);
$pdf->Pembuat(
$this->session->userdata('nama') ?: 'Administrator',
'Finance Department'
);
$filename = 'Neraca_Saldo_'.date('YmdHis').'.pdf';
$pdf->Output('I',$filename);
exit;
}
}
+184 -96
View File
@@ -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);
@@ -526,10 +529,11 @@ class Invoices extends CI_Controller {
// =========================
public function get_items($id)
{
$this->db->select('invoice_details.*, accounts.kode_akun, accounts.nama_akun, invoices.status');
$this->db->select('invoice_details.*, accounts.kode_akun, accounts.nama_akun, invoices.status, item_barcodes.barcode');
$this->db->from('invoice_details');
$this->db->join('accounts', 'accounts.id = invoice_details.account_id', 'left');
$this->db->join('invoices', 'invoices.id = invoice_details.invoice_id', 'left');
$this->db->join('item_barcodes', 'item_barcodes.id = invoice_details.barcode_id', 'left');
$this->db->where('invoice_details.invoice_id', $id);
$this->db->order_by('tanggal','ASC');
$this->db->order_by('id','ASC');
@@ -554,6 +558,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 +566,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 +583,49 @@ 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]);
if ($status_bar == 'sold_out') {
$this->db->where('barcode', $itemBar->barcode)->where('status', 'active')->update('item_technician', ['status' => 'inactive', 'deleted_at' => date('Y-m-d H:i:s')]);
}
// END - Item Movement & item barcodes update status
$total_hpp = $qty * $item->harga_beli;
}
@@ -600,6 +644,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 +671,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 +687,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 +699,7 @@ class Invoices extends CI_Controller {
$tenor,
$tanggal,
$account_id,
$total_hpp,
$item_id ?: null
);
}
@@ -675,105 +722,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 +842,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){
@@ -842,11 +922,13 @@ class Invoices extends CI_Controller {
->select('
invoice_details.*,
customers.nama,
invoices.no_invoice
invoices.no_invoice,
item_barcodes.barcode
')
->from('invoice_details')
->join('invoices', 'invoices.id = invoice_details.invoice_id')
->join('customers', 'customers.id = invoices.customer_id')
->join('item_barcodes', 'item_barcodes.id = invoice_details.barcode_id', 'left')
->where('invoice_details.id', $id)
->get()
->row();
@@ -854,6 +936,7 @@ class Invoices extends CI_Controller {
if(!$d) return;
$total_hpp = 0;
$barcode = '';
// =========================
// 🔥 TENTUKAN NILAI JURNAL
@@ -883,8 +966,10 @@ class Invoices extends CI_Controller {
'ref_type' => 'invoice_details',
'ref_id' => $id,
'warehouse_id' => $d->warehouse_id,
'keterangan' => 'Penjualan ' . $item->nama_barang . ' kepada ' . $d->nama . ' - Invoice ' . $d->no_invoice . ' - Tgl ' . $d->tanggal
'keterangan' => 'Penjualan ' . $item->nama_barang . ' kepada ' . $d->nama . ' - Invoice ' . $d->no_invoice . ' - Tgl ' . $d->tanggal . ' - Kode ' . $d->barcode
]);
$barcode = ' - Kode: ' . $d->barcode;
$this->update_item_stok($d->items_id);
}
@@ -901,7 +986,7 @@ class Invoices extends CI_Controller {
$this->db->insert('journals', [
'tanggal' => $d->tanggal,
'no_ref' => $no_ref,
'keterangan' => 'Penjualan '.$nama_item_jr,
'keterangan' => 'Penjualan '.$nama_item_jr . $barcode . ' - Kepada: ' . $d->nama,
'ref_type' => 'invoice_details',
'ref_id' => $id,
'created_by' => $this->session->userdata('user_id')
@@ -1108,7 +1193,7 @@ class Invoices extends CI_Controller {
public function get_accounts_assets()
{
$data = $this->db
->where_in('kode_akun', [101,102,207,201,202,203,204,205])
->where_in('tipe', ['asset','liability'])
->order_by('kode_akun', 'ASC')
->get('accounts')
->result();
@@ -1223,7 +1308,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 +1335,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 +1422,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 +1585,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')
File diff suppressed because it is too large Load Diff
+97 -24
View File
@@ -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',
'
<button class="btn btn-sm btn-secondary btn-editkode" data-id="'.$row->id.'">Edit</button>
<button class="btn btn-sm btn-danger btn-delete" data-id="'.$row->id.'">Delete</button>
@@ -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']);
}
+35 -68
View File
@@ -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);
}
}
+518
View File
@@ -0,0 +1,518 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Teknisi extends CI_Controller {
public function __construct()
{
parent::__construct();
$this->load->model('PeralatanTeknisi_model', 'ptm');
if (!$this->session->userdata('logged_in')) {
redirect('auth');
}
}
public function index()
{
$data = [
"active_menu" => "peralatan_teknisi",
"teknisi_list" => $this->ptm->get_teknisi_list()
];
$this->load->view('partials/header', $data);
$this->load->view('peralatan_teknisi/layout', $data);
$this->load->view('partials/footer');
}
public function get_data()
{
$user_id = $this->input->get('user_id');
$status = $this->input->get('status') ?: 'active';
log_message('debug', '[get_data] user_id: ' . var_export($user_id, true) . ', status: ' . $status);
$data = $this->ptm->get_barang_dibawa($user_id, $status);
log_message('debug', '[get_data] query returned ' . count($data) . ' rows');
$result = [];
$no = 1;
foreach ($data as $row) {
$result[] = [
$no++,
htmlspecialchars($row->barcode, ENT_QUOTES, 'UTF-8'),
htmlspecialchars($row->nama_barang, ENT_QUOTES, 'UTF-8'),
htmlspecialchars($row->serial_number, ENT_QUOTES, 'UTF-8'),
];
}
log_message('debug', '[get_data] response: ' . json_encode(['data' => $result]));
echo json_encode(['data' => $result]);
}
public function scan_item()
{
$barcode = trim($this->input->get('barcode'));
if (empty($barcode)) {
echo json_encode(['status' => false, 'message' => 'QR Code tidak boleh kosong']);
return;
}
$item = $this->ptm->get_item_by_barcode($barcode);
if (!$item) {
echo json_encode(['status' => false, 'message' => 'Barcode tidak ditemukan']);
return;
}
$teknisi = $this->ptm->get_current_teknisi($item->barcode);
echo json_encode([
'status' => true,
'message' => 'Barang ditemukan',
'data' => [
'barcode' => $item->barcode,
'nama_barang' => $item->nama_barang,
'serial_number' => $item->serial_number,
'barcode_id' => $item->barcode_id,
'item_id' => $item->item_id,
'barcode_status' => $item->barcode_status,
'teknisi_id' => $teknisi ? $teknisi->user_id : null,
'teknisi_name' => $teknisi ? $teknisi->full_name : null,
'teknisi_code' => $teknisi ? $teknisi->employee_code : null
]
]);
}
function get_date_by_employee_id()
{
$user_id = (int)$this->input->get('user_id');
if (!$user_id) {
echo json_encode(['status' => false, 'message' => 'ID teknisi tidak valid']);
return;
}
$dates = $this->ptm->get_dates_by_employee_id($user_id);
echo json_encode(['status' => true, 'data' => $dates]);
}
public function batch_kembalikan()
{
$barcodes = $this->input->post('barcodes');
if (empty($barcodes) || !is_array($barcodes)) {
echo json_encode(['status' => false, 'message' => 'Tidak ada barcode yang dipilih']);
return;
}
$result = $this->ptm->batch_kembalikan($barcodes);
echo json_encode($result);
}
public function batch_dibawa()
{
$barcodes = $this->input->post('barcodes');
$user_id = (int)$this->input->post('user_id');
if (empty($barcodes) || !is_array($barcodes)) {
echo json_encode(['status' => false, 'message' => 'Tidak ada barcode yang dipilih']);
return;
}
if (empty($user_id)) {
echo json_encode(['status' => false, 'message' => 'Pilih teknisi terlebih dahulu']);
return;
}
$result = $this->ptm->batch_dibawa($barcodes, $user_id);
echo json_encode($result);
}
public function get_teknisi_table()
{
$draw = (int)$this->input->get('draw');
$start = (int)$this->input->get('start');
$length = (int)$this->input->get('length');
$search = $this->input->get('search')['value'] ?? '';
$order_col = (int)$this->input->get('order')[0]['column'] ?? 1;
$order_dir = $this->input->get('order')[0]['dir'] ?? 'asc';
$data = $this->ptm->get_teknisi_table_data($start, $length, $search, $order_col, $order_dir);
$total_filtered = $this->ptm->count_filtered_teknisi($search);
$total_all = $this->ptm->count_all_teknisi();
$result = [];
$no = $start + 1;
foreach ($data as $row) {
$item_count = (int)$row->item_count;
$item_text = $item_count . ' Item';
$aksi = '<button class="btn btn-info btn-sm btn-detail-teknisi" data-id="'.$row->id.'" title="Detail">
<i class="bi bi-eye"></i>
</button>
<button class="btn btn-warning btn-sm btn-form-barang-bawaan" data-id="'.$row->id.'" data-nama="'.htmlspecialchars($row->full_name, ENT_QUOTES, 'UTF-8').'" title="Form Barang Bawaan">
<i class="bi bi-file-earmark-text"></i>
</button>';
$result[] = [
$no++,
htmlspecialchars($row->full_name, ENT_QUOTES, 'UTF-8'),
htmlspecialchars($row->employee_code, ENT_QUOTES, 'UTF-8'),
htmlspecialchars($row->department_name, ENT_QUOTES, 'UTF-8') ?: '-',
$item_text,
$aksi
];
}
echo json_encode([
'draw' => $draw,
'recordsTotal' => $total_all,
'recordsFiltered' => $total_filtered,
'data' => $result
]);
}
public function teknisi_detail()
{
$user_id = (int)$this->input->get('user_id');
if (!$user_id) {
echo json_encode(null);
return;
}
$info = $this->ptm->get_teknisi_info($user_id);
echo json_encode($info);
}
public function get_teknisi_items()
{
$user_id = (int)$this->input->get('user_id');
$draw = (int)$this->input->get('draw');
$start = (int)$this->input->get('start');
$length = (int)$this->input->get('length');
$search = $this->input->get('search')['value'] ?? '';
$order_col = (int)$this->input->get('order')[0]['column'] ?? 2;
$order_dir = $this->input->get('order')[0]['dir'] ?? 'asc';
if (!$user_id) {
echo json_encode(['draw' => $draw, 'recordsTotal' => 0, 'recordsFiltered' => 0, 'data' => []]);
return;
}
$data = $this->ptm->get_teknisi_items_data($user_id, $start, $length, $search, $order_col, $order_dir);
$total_filtered = $this->ptm->count_filtered_teknisi_items($user_id, $search);
$total_all = $this->ptm->count_all_teknisi_items($user_id);
$result = [];
$no = $start + 1;
foreach ($data as $row) {
$badge = $row->status == 'active'
? '<span class="badge bg-success">Dibawa</span>'
: '<span class="badge bg-secondary">' . htmlspecialchars($row->status, ENT_QUOTES, 'UTF-8') . '</span>';
$result[] = [
$no++,
$row->tanggal ?? date('d/m/Y'),
htmlspecialchars($row->barcode, ENT_QUOTES, 'UTF-8'),
htmlspecialchars($row->nama_barang, ENT_QUOTES, 'UTF-8'),
htmlspecialchars($row->serial_number, ENT_QUOTES, 'UTF-8') ?: '-',
htmlspecialchars($row->qty_sisa, ENT_QUOTES, 'UTF-8') ?: '0',
$badge
];
}
echo json_encode([
'draw' => $draw,
'recordsTotal' => $total_all,
'recordsFiltered' => $total_filtered,
'data' => $result
]);
}
public function get_form_barang_bawaan_data()
{
$user_id = (int)$this->input->get('user_id');
if (!$user_id) {
echo json_encode(['status' => false, 'message' => 'ID teknisi tidak valid']);
return;
}
$info = $this->ptm->get_teknisi_info($user_id);
if (!$info) {
echo json_encode(['status' => false, 'message' => 'Teknisi tidak ditemukan']);
return;
}
$tanggal_param = $this->input->get('tanggal');
$items = [];
if ($tanggal_param) {
$items = $this->ptm->get_teknisi_items_full($user_id, $tanggal_param);
}
if ($tanggal_param) {
if ($tanggal_param === 'all') {
$noFaktur = 'GRJN/K/ALL' . date('/m/Y');
$tanggal = date('d F Y');
} else {
$ts = strtotime($tanggal_param);
$noFaktur = 'GRJN/K/' . date('d/m/Y', $ts);
$tanggal = date('d F Y', $ts);
}
} else {
$noFaktur = 'GRJN/K/' . date('d/m/Y');
$tanggal = date('d F Y');
}
$itemsData = [];
$no = 1;
foreach ($items as $item) {
$itemsData[] = [
'no' => $no++,
'tanggal' => $item->tanggal ?? date('d/m/Y'),
'barcode' => $item->barcode ?? '-',
'nama_barang' => $item->nama_barang ?? '-',
'serial_number' => $item->serial_number ?? '-',
'jumlah' => isset($item->qty_sisa) ? (int)$item->qty_sisa : 1,
'keterangan' => ''
];
}
echo json_encode([
'status' => true,
'data' => [
'no_faktur' => $noFaktur,
'tanggal' => $tanggal,
'teknisi' => $info->full_name,
'teknisi_id' => $info->id,
'employee_code' => $info->employee_code,
'position_name' => $info->position_name,
'department_name' => $info->department_name,
'total_item' => count($itemsData),
'items' => $itemsData
]
]);
}
public function generate_pdf_barang_bawaan($user_id = null)
{
$user_id = (int)$user_id;
if (!$user_id) {
show_error('ID teknisi tidak valid', 400);
}
if (ob_get_length()) {
ob_end_clean();
}
$info = $this->ptm->get_teknisi_info($user_id);
if (!$info) {
show_error('Teknisi tidak ditemukan', 404);
}
$tanggal_param = $this->input->get('tanggal');
if ($tanggal_param === 'all') {
$items = $this->ptm->get_teknisi_items_full($user_id);
$noFaktur = 'GRJN/K/ALL' . date('/m/Y');
$tanggal = 'Semua Tanggal';
} elseif ($tanggal_param) {
$ts = strtotime($tanggal_param);
$items = $this->ptm->get_teknisi_items_full($user_id, $tanggal_param);
$noFaktur = 'GRJN/K/' . date('d/m/Y', $ts);
$tanggal = date('d/m/Y', $ts);
} else {
$items = $this->ptm->get_teknisi_items_full($user_id);
$noFaktur = 'GRJN/K/' . date('d/m/Y');
$tanggal = date('d/m/Y');
}
require_once(APPPATH . 'third_party/fpdf/fpdf.php');
require_once(APPPATH . 'libraries/PDF_BarangBawaan.php');
$pdf = new PDF_BarangBawaan('L', 'mm', 'A5');
$pdf->AliasNbPages();
$pdf->SetMargins(5, 5, 5);
$pdf->SetAutoPageBreak(true, 10);
$pdf->setFormInfo($noFaktur, $tanggal, $info->full_name);
$pdf->AddPage('L', 'A5');
$pdf->BarangBawaanTable($items);
$pdf->SignatureArea();
$namaFile = 'FORM_BARANG_BAWAAN_' . str_replace(' ', '_', $info->full_name) . '_' . date('Ymd') . '.pdf';
$pdf->Output('I', $namaFile);
exit;
}
public function get_transfer_data()
{
$data = $this->ptm->get_transfer_data();
$result = [];
$no = 1;
foreach ($data as $row) {
$result[] = [
$no++,
htmlspecialchars($row->barcode, ENT_QUOTES, 'UTF-8'),
htmlspecialchars($row->nama_barang, ENT_QUOTES, 'UTF-8'),
htmlspecialchars(($row->from_code ?? '') . ' - ' . ($row->from_name ?? ''), ENT_QUOTES, 'UTF-8'),
htmlspecialchars(($row->to_code ?? '') . ' - ' . ($row->to_name ?? ''), ENT_QUOTES, 'UTF-8'),
htmlspecialchars($row->created_at, ENT_QUOTES, 'UTF-8'),
'<span class="badge bg-success">Selesai</span>'
];
}
echo json_encode(['data' => $result]);
}
public function batch_transfer()
{
$barcodes = $this->input->post('barcodes');
$from_user_id = (int)$this->input->post('from_user_id');
$to_user_id = (int)$this->input->post('to_user_id');
$technician = $this->ptm->get_teknisi_info($to_user_id);
log_message('debug', 'batch_transfer POST: ' . json_encode([
'barcodes' => $barcodes,
'from_user_id' => $from_user_id,
'to_user_id' => $to_user_id
]));
if (empty($barcodes) || !is_array($barcodes)) {
echo json_encode(['status' => false, 'message' => 'Tidak ada barcode yang dipilih']);
return;
}
if (empty($from_user_id) || empty($to_user_id)) {
echo json_encode(['status' => false, 'message' => 'Pilih teknisi asal dan tujuan']);
return;
}
if ($from_user_id == $to_user_id) {
echo json_encode(['status' => false, 'message' => 'Teknisi tujuan tidak boleh sama dengan teknisi asal']);
return;
}
$now = date('Y-m-d H:i:s');
$errors = [];
$this->db->trans_start();
foreach ($barcodes as $barcode) {
$barcode = trim($barcode);
if ($barcode == '') continue;
$item = $this->db
->select('it.*, ib.id as barcode_id, tec.full_name as teknisi_name')
->from('item_technician it')
->join('item_barcodes ib', 'ib.barcode = it.barcode', 'left')
->join('k_employees tec', 'tec.id = it.user_id', 'left')
->where('it.barcode', $barcode)
->where('it.status', 'active')
->where('it.user_id', $from_user_id)
->get()
->row();
if (!$item) {
$errors[] = "Barcode $barcode tidak ditemukan pada teknisi asal";
continue;
}
log_message('debug', 'batch_transfer item: ' . json_encode($item));
$this->db
->where('barcode', $item->barcode)
->where('user_id', $from_user_id)
->where('status', 'active')
->update('item_technician', [
'user_id' => $to_user_id
]);
$this->db->insert('item_movements', [
'item_id' => $item->item_id,
'barcode_id' => $item->barcode_id,
'qty' => 1,
'from_type' => 'technician',
'from_id' => $from_user_id,
'to_type' => 'technician',
'to_id' => $to_user_id,
'movement_type' => 'transfer',
'notes' => 'Transfer Barang dari: ' . $item->teknisi_name . ' ke ' . $technician->full_name,
'created_at' => $now
]);
}
$this->db->trans_complete();
if ($this->db->trans_status() === FALSE) {
$db_error = $this->db->error();
log_message('error', 'batch_transfer gagal: ' . json_encode($db_error));
echo json_encode([
'status' => false,
'message' => 'Gagal memproses transfer',
'debug' => $db_error['message'] ?? 'Unknown database error'
]);
return;
}
$success_count = count($barcodes) - count($errors);
$msg = "$success_count barang berhasil ditransfer";
if (count($errors) > 0) {
$msg .= '. ' . implode(', ', $errors);
}
echo json_encode(['status' => true, 'message' => $msg, 'errors' => $errors]);
}
public function get_log_data()
{
$data = $this->ptm->get_log_barang();
$result = [];
$no = 1;
foreach ($data as $row) {
$status_badge = $row->status === 'active'
? '<span class="badge bg-success">ACTIVE</span>'
: '<span class="badge bg-secondary">INACTIVE</span>';
$result[] = [
$no++,
htmlspecialchars($row->teknisi_name ?? '-', ENT_QUOTES, 'UTF-8'),
htmlspecialchars($row->item_code ?? '-', ENT_QUOTES, 'UTF-8'),
htmlspecialchars($row->barcode, ENT_QUOTES, 'UTF-8'),
htmlspecialchars($row->nama_barang ?? '-', ENT_QUOTES, 'UTF-8'),
$row->created_at ? date('d/m/Y H:i', strtotime($row->created_at)) : '-',
$row->status === 'inactive' && $row->deleted_at ? date('d/m/Y H:i', strtotime($row->deleted_at)) : '-',
$status_badge,
];
}
echo json_encode(['data' => $result]);
}
public function log_barang()
{
$data = [
"active_menu" => "peralatan_teknisi",
];
$this->load->view('partials/header', $data);
$this->load->view('peralatan_teknisi/log_barang', $data);
$this->load->view('partials/footer');
}
}
+210
View File
@@ -0,0 +1,210 @@
<?php
require_once(APPPATH . 'third_party/fpdf/fpdf.php');
class PDF_BarangBawaan extends FPDF
{
private $noFaktur;
private $tanggal;
private $teknisi;
private $colWidths = [8, 18, 27, 42, 24, 10, 46, 15];
public function __construct($orientation = 'L', $unit = 'mm', $size = 'A5')
{
parent::__construct($orientation, $unit, $size);
$this->SetDisplayMode('fullpage');
}
function SetMargins($left, $top, $right = null)
{
parent::SetMargins(10, $top, 10);
}
public function setFormInfo($noFaktur, $tanggal, $teknisi)
{
$this->noFaktur = $noFaktur;
$this->tanggal = $tanggal;
$this->teknisi = $teknisi;
}
function Header()
{
$this->SetFont('Arial', 'B', 11);
$this->SetTextColor(245, 140, 0);
$this->Cell(0, 6, 'FORM BARANG BAWAAN TEKNISI', 0, 1, 'L');
$this->SetTextColor(0, 0, 0);
$this->Ln(2);
$this->SetFont('Arial', '', 8);
$this->Cell(0, 5, 'Tanggal : ' . $this->tanggal . ' Teknisi : ' . $this->teknisi . ' No. Faktur : ' . $this->noFaktur, 0, 1, 'C');
$this->Ln(2);
$this->_drawTableHeader();
}
function Footer()
{
$this->SetY(-8);
$this->SetFont('Arial', 'I', 7);
$this->Cell(0, 4, 'Hal ' . $this->PageNo(), 0, 0, 'C');
}
private function _drawTableHeader()
{
$startX = $this->lMargin;
$this->SetX($startX);
$this->SetFillColor(245, 140, 0);
$this->SetTextColor(255, 255, 255);
$this->SetFont('Arial', 'B', 7);
$headers = ['No', 'Tanggal', 'Barcode', 'Nama Barang', 'Serial Number', 'Jumlah', 'Keterangan', 'Server'];
foreach ($headers as $i => $h) {
$this->Cell($this->colWidths[$i], 7, $h, 1, 0, 'C', true);
}
$this->Ln();
$this->SetTextColor(0, 0, 0);
}
public function BarangBawaanTable($items)
{
$this->SetFont('Arial', '', 7);
$lineH = 5.5;
$minRowH = 8;
$no = 1;
$colsMultiCell = [false, false, true, true, true, false, true, false];
$cellAligns = ['C', 'C', 'L', 'L', 'L', 'C', 'L', 'L'];
foreach ($items as $item) {
$keterangan = isset($item->keterangan) ? $item->keterangan : '';
$namaBarang = $item->nama_barang ?? '-';
$serial = $item->serial_number ?? '-';
$barcode = $item->barcode ?? '-';
$jumlah = isset($item->qty_sisa) ? (int)$item->qty_sisa : 1;
$server = '';
$cellValues = [
(string)$no,
$item->tanggal ?? '-',
$barcode,
$namaBarang,
$serial,
(string)$jumlah,
$keterangan,
$server
];
$maxLines = 1;
foreach ($cellValues as $i => $val) {
if ($colsMultiCell[$i]) {
$lines = $this->NbLines($this->colWidths[$i], $val);
if ($lines > $maxLines) {
$maxLines = $lines;
}
}
}
$rowH = max($minRowH, $maxLines * $lineH);
if ($this->GetY() + $rowH > $this->PageBreakTrigger) {
$this->AddPage();
}
$startX = $this->lMargin;
$startY = $this->GetY();
$currentX = $startX;
foreach ($this->colWidths as $i => $w) {
$this->Rect($currentX, $startY, $w, $rowH);
$currentX += $w;
}
$currentX = $startX;
foreach ($cellValues as $i => $val) {
$this->SetXY($currentX, $startY);
if ($colsMultiCell[$i]) {
$colLines = $this->NbLines($this->colWidths[$i], $val);
$contentH = $colLines * $lineH;
$offsetY = ($rowH - $contentH) / 2;
$this->SetXY($currentX, $startY + $offsetY);
$this->MultiCell($this->colWidths[$i], $lineH, $val, 0, $cellAligns[$i]);
} else {
$this->Cell($this->colWidths[$i], $rowH, $val, 0, 0, $cellAligns[$i]);
}
$currentX += $this->colWidths[$i];
}
$this->SetXY($startX, $startY + $rowH);
$no++;
}
}
public function SignatureArea()
{
$this->Ln(6);
$sigW = 56;
$gapW = 11;
$blockW = 3 * $sigW + 2 * $gapW;
$startX = $this->lMargin + ($this->w - $this->lMargin - $this->rMargin - $blockW) / 2;
$signatures = [
['title' => 'Admin Gudang', 'name' => 'Gono Soewisto'],
['title' => 'Leader Teknisi', 'name' => 'Chandra Nursani'],
['title' => 'Mengetahui', 'name' => 'Tito Ngudiana'],
];
$sigY = $this->GetY();
foreach ($signatures as $i => $sig) {
$x = $startX + $i * ($sigW + $gapW);
$this->SetXY($x, $sigY);
$this->SetFont('Arial', '', 8);
$this->Cell($sigW, 5, $sig['title'], 0, 1, 'C');
$this->SetX($x);
$this->Ln(10);
$this->SetX($x);
$this->SetFont('Arial', 'U', 8);
$this->Cell($sigW, 5, ' ', 0, 1, 'C');
$this->SetX($x);
$this->SetFont('Arial', 'I', 7);
$this->Cell($sigW, 4, $sig['name'], 0, 1, 'C');
}
$this->SetY($sigY + 5 + 10 + 5 + 4);
}
private function NbLines($w, $txt)
{
$cw = &$this->CurrentFont['cw'];
if ($w == 0) {
$w = $this->w - $this->rMargin - $this->x;
}
$wmax = ($w - 2 * $this->cMargin) * 1000 / $this->FontSize;
$s = str_replace("\r", '', $txt);
$nb = strlen($s);
if ($nb > 0 && $s[$nb - 1] == "\n") { $nb--; }
$sep = -1;
$i = 0;
$j = 0;
$l = 0;
$nl = 1;
while ($i < $nb) {
$c = $s[$i];
if ($c == "\n") { $i++; $sep = -1; $j = $i; $l = 0; $nl++; continue; }
if ($c == ' ') { $sep = $i; }
$l += isset($cw[ord($c)]) ? $cw[ord($c)] : 600;
if ($l > $wmax) {
if ($sep == -1) { if ($i == $j) $i++; }
else { $i = $sep + 1; }
$sep = -1; $j = $i; $l = 0; $nl++;
} else { $i++; }
}
return $nl;
}
}
+329
View File
@@ -0,0 +1,329 @@
<?php
require_once(APPPATH . 'third_party/fpdf/fpdf.php');
class PDF extends FPDF
{
// ----------------------------------------------------------------
// PROPERTI HEADER
// ----------------------------------------------------------------
private $nama;
private $alamat;
private $logo;
// ----------------------------------------------------------------
// PROPERTI TABEL
// ----------------------------------------------------------------
private $isTable = false;
private $tableWidth = [];
private $tableHeader = [];
// Konfigurasi tabel A4 Portrait (lebar efektif = 190mm, margin 10+10)
// Tanggal : 22 | No Ref : 28 | Keterangan : 60 | Debit : 26 | Kredit : 26 | Saldo : 28 → TOTAL : 190
private $colWidths = [22, 28, 60, 26, 26, 28];
// ----------------------------------------------------------------
// PROPERTI INFO LAPORAN
// ----------------------------------------------------------------
private $namaAkun = '';
private $periodeStr = '';
// ----------------------------------------------------------------
// SETTER
// ----------------------------------------------------------------
public function setHeaderData($nama, $alamat, $logo = '')
{
$this->nama = $nama;
$this->alamat = $alamat;
$this->logo = $logo;
}
public function setInfoLaporan($namaAkun, $periodeStr)
{
$this->namaAkun = $namaAkun;
$this->periodeStr = $periodeStr;
}
public function setTableHeader($header, $width)
{
$this->tableHeader = $header;
$this->tableWidth = $width;
}
// ----------------------------------------------------------------
// HEADER HALAMAN
// ----------------------------------------------------------------
function Header()
{
// Lebar halaman A4 Portrait = 210mm (area cetak 10..200)
$pageRight = 200;
// 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
$this->Ln(2);
$this->SetLineWidth(0.6);
$this->Line(10, $this->GetY(), $pageRight, $this->GetY());
$this->SetLineWidth(0.2);
$this->Line(10, $this->GetY() + 1, $pageRight, $this->GetY() + 1);
$this->Ln(5);
// Judul
$this->SetFont('Arial', 'B', 13);
$this->SetTextColor(245, 140, 0);
$this->Cell(0, 7, 'LAPORAN BUKU BESAR', 0, 1, 'C');
$this->SetTextColor(0, 0, 0);
// Periode
$this->SetFont('Arial', '', 10);
$this->Cell(0, 5, $this->periodeStr, 0, 1, 'C');
$this->Ln(3);
// Info akun & tanggal cetak
$colLabel = 35;
$colSep = 5;
$startX = ($this->GetPageWidth() - array_sum($this->colWidths)) / 2;
$this->SetX($startX);
$this->SetFont('Arial', 'B', 10);
$this->Cell($colLabel, 6, 'Akun');
$this->SetFont('Arial', '', 10);
$this->Cell($colSep, 6, ':');
$this->Cell(0, 6, $this->namaAkun, 0, 1);
$this->SetX($startX);
$this->SetFont('Arial', 'B', 10);
$this->Cell($colLabel, 6, 'Di Cetak Pada');
$this->SetFont('Arial', '', 10);
$this->Cell($colSep, 6, ':');
$this->Cell(0, 6, date('d-m-Y') . ' Jam ' . date('H:i:s'), 0, 1);
$this->Ln(3);
if ($this->isTable && !empty($this->tableHeader)) {
$this->_drawTableHeader();
}
}
// ----------------------------------------------------------------
// FOOTER HALAMAN
// ----------------------------------------------------------------
function Footer()
{
$this->SetY(-10);
$this->SetFont('Arial', 'I', 8);
$this->Cell(0, 5, 'Halaman ' . $this->PageNo(), 0, 0, 'C');
}
// ----------------------------------------------------------------
// HELPER: Gambar baris header tabel
// ----------------------------------------------------------------
private function _drawTableHeader()
{
$startX = ($this->GetPageWidth() - array_sum($this->colWidths)) / 2;
$this->SetX($startX);
$this->SetFillColor(245, 140, 0);
$this->SetTextColor(255, 255, 255);
$this->SetFont('Arial', 'B', 9);
foreach ($this->tableHeader as $k => $v) {
$this->Cell($this->tableWidth[$k], 8, $v, 1, 0, 'C', true);
}
$this->SetTextColor(0, 0, 0);
$this->Ln();
}
// ----------------------------------------------------------------
// TABEL BUKU BESAR
// ----------------------------------------------------------------
public function BukuBesarTable($result)
{
$header = ['Tanggal', 'No Ref', 'Keterangan', 'Debit', 'Kredit', 'Saldo'];
$this->isTable = true;
$this->setTableHeader($header, $this->colWidths);
// Header halaman pertama
$this->_drawTableHeader();
$startX = ($this->GetPageWidth() - array_sum($this->colWidths)) / 2;
$lineH = 5;
$this->SetFont('Arial', '', 9);
if (empty($result)) {
$this->SetX($startX);
$this->SetFont('Arial', 'B', 10);
$this->Cell(array_sum($this->colWidths), 8, 'Tidak ada data', 1, 1, 'C');
$this->isTable = false;
return;
}
$fill = false;
// Batas bawah A4 Portrait (297mm) dikurangi area footer
$pageBreakTrigger = 277;
foreach ($result as $row) {
// Hitung tinggi baris berdasar No Ref & Keterangan (keduanya MultiCell)
$nbRef = $this->NbLines($this->colWidths[1], $row['no_ref']);
$nbKet = $this->NbLines($this->colWidths[2], $row['keterangan']);
$nbLines = max(1, $nbRef, $nbKet);
$rowH = $nbLines * $lineH;
// Ganti halaman jika mendekati batas bawah
if ($this->GetY() + $rowH > $pageBreakTrigger) {
$this->AddPage('P', 'A4');
}
if ($fill) {
$this->SetFillColor(245, 245, 245);
}
$x = $startX;
$y = $this->GetY();
// Tanggal
$this->SetX($x);
$this->Cell($this->colWidths[0], $rowH, date('d-m-Y', strtotime($row['tanggal'])), 1, 0, 'C', $fill);
// No Ref (MultiCell)
$xRef = $this->GetX();
$offsetRef = ($rowH - $nbRef * $lineH) / 2;
// gambar border kotak dulu
$this->Rect($xRef, $y, $this->colWidths[1], $rowH, $fill ? 'DF' : 'D');
$this->SetXY($xRef, $y + $offsetRef);
$this->MultiCell($this->colWidths[1], $lineH, $row['no_ref'], 0, 'C', false);
// Keterangan (MultiCell)
$xKet = $xRef + $this->colWidths[1];
$offsetKet = ($rowH - $nbKet * $lineH) / 2;
$this->Rect($xKet, $y, $this->colWidths[2], $rowH, $fill ? 'DF' : 'D');
$this->SetXY($xKet, $y + $offsetKet);
$this->MultiCell($this->colWidths[2], $lineH, $row['keterangan'], 0, 'L', false);
// Kolom angka
$this->SetXY($xKet + $this->colWidths[2], $y);
$this->Cell($this->colWidths[3], $rowH, number_format($row['debit'], 2, ',', '.'), 1, 0, 'R', $fill);
$this->Cell($this->colWidths[4], $rowH, number_format($row['kredit'], 2, ',', '.'), 1, 0, 'R', $fill);
$this->Cell($this->colWidths[5], $rowH, number_format($row['saldo'], 2, ',', '.'), 1, 0, 'R', $fill);
$this->SetXY($x, $y + $rowH);
$fill = !$fill;
}
$this->isTable = false;
}
// ----------------------------------------------------------------
// BARIS TOTAL
// ----------------------------------------------------------------
public function BukuBesarTotal($totalDebit, $totalKredit, $saldo)
{
$startX = ($this->GetPageWidth() - array_sum($this->colWidths)) / 2;
$labelWidth = $this->colWidths[0] + $this->colWidths[1] + $this->colWidths[2];
$this->SetX($startX);
$this->SetFillColor(255, 204, 102);
$this->SetFont('Arial', 'B', 9);
$this->Cell($labelWidth, 8, 'TOTAL', 1, 0, 'C', true);
$this->Cell($this->colWidths[3], 8, number_format($totalDebit, 2, ',', '.'), 1, 0, 'R', true);
$this->Cell($this->colWidths[4], 8, number_format($totalKredit, 2, ',', '.'), 1, 0, 'R', true);
$this->Cell($this->colWidths[5], 8, number_format($saldo, 2, ',', '.'), 1, 1, 'R', true);
}
// ----------------------------------------------------------------
// TANDA TANGAN
// ----------------------------------------------------------------
public function Pembuat($nama, $jabatan = '')
{
$signWidth = 70;
$signX = 125; // disesuaikan untuk A4 Portrait (210mm)
$this->Ln(10);
$this->SetX($signX);
$this->SetFont('Arial', '', 10);
$this->Cell($signWidth, 6, 'Mengetahui,', 0, 1, 'C');
$this->Ln(10);
$this->SetX($signX);
$this->SetFont('Arial', 'B', 10);
$this->Cell($signWidth, 6, $nama, 0, 1, 'C');
if (!empty($jabatan)) {
$this->SetX($signX);
$this->SetFont('Arial', '', 9);
$this->Cell($signWidth, 5, '(' . $jabatan . ')', 0, 1, 'C');
}
}
// ----------------------------------------------------------------
// HELPER: Hitung jumlah baris MultiCell
// ----------------------------------------------------------------
private function NbLines($w, $txt)
{
$cw = &$this->CurrentFont['cw'];
if ($w == 0) {
$w = $this->w - $this->rMargin - $this->x;
}
$wmax = ($w - 2 * $this->cMargin) * 1000 / $this->FontSize;
$s = str_replace("\r", '', (string)$txt);
$nb = strlen($s);
if ($nb > 0 && $s[$nb - 1] == "\n") {
$nb--;
}
$sep = -1;
$i = 0; $j = 0; $l = 0; $nl = 1;
while ($i < $nb) {
$c = $s[$i];
if ($c == "\n") {
$i++; $sep = -1; $j = $i; $l = 0; $nl++;
continue;
}
if ($c == ' ') {
$sep = $i;
}
$l += isset($cw[ord($c)]) ? $cw[ord($c)] : 600;
if ($l > $wmax) {
if ($sep == -1) {
if ($i == $j) $i++;
} else {
$i = $sep + 1;
}
$sep = -1; $j = $i; $l = 0; $nl++;
} else {
$i++;
}
}
return $nl;
}
}
+216
View File
@@ -0,0 +1,216 @@
<?php
require_once(APPPATH.'third_party/fpdf/fpdf.php');
class PDF extends FPDF
{
private $nama;
private $alamat;
private $telepon;
private $logo;
private $isTable = false;
private $tableWidth = [];
private $tableHeader = [];
// Lebar kertas A4 Portrait = 210mm, margin kiri+kanan = 10+10 = 20mm
// Area cetak efektif = 190mm
private $pageWidth = 190;
// ----------------------------------------------------------------
// SETTER
// ----------------------------------------------------------------
public function setHeaderData($nama, $alamat, $telepon, $logo = '')
{
$this->nama = $nama;
$this->alamat = $alamat;
$this->telepon = $telepon;
$this->logo = $logo;
}
public function setTableHeader($header, $width)
{
$this->tableHeader = $header;
$this->tableWidth = $width;
}
// ----------------------------------------------------------------
// HEADER HALAMAN
// ----------------------------------------------------------------
function Header()
{
// Logo (opsional)
if (!empty($this->logo) && file_exists($this->logo)) {
$this->Image($this->logo, 10, 8, 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');
$this->SetX($x);
$this->Cell(0, 5, 'Telp : ' . $this->telepon, 0, 1, 'L');
// Garis pemisah header (A4 Portrait: dari 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 laporan
$this->SetFont('Arial', 'B', 14);
$this->SetTextColor(245, 140, 0);
$this->Cell(0, 8, 'LAPORAN DATA BARANG', 0, 1, 'C');
$this->SetTextColor(0, 0, 0);
// Tanggal cetak
$this->SetFont('Arial', '', 9);
$this->Cell(0, 5, 'DIcetak Pada : ' . date('d-m-Y') . ' - ' . date('H:i:s'), 0, 1, 'C');
$this->Ln(3);
// Header tabel (muncul otomatis saat ganti halaman)
if ($this->isTable && !empty($this->tableHeader)) {
$this->_drawTableHeader();
}
}
// ----------------------------------------------------------------
// FOOTER HALAMAN
// ----------------------------------------------------------------
function Footer()
{
$this->SetY(-10);
$this->SetFont('Arial', 'I', 8);
$this->Cell(0, 5, 'Halaman ' . $this->PageNo(), 0, 0, 'C');
}
// ----------------------------------------------------------------
// HELPER: Gambar baris header tabel
// ----------------------------------------------------------------
private function _drawTableHeader()
{
$this->SetFillColor(245, 140, 0);
$this->SetTextColor(255, 255, 255);
$this->SetFont('Arial', 'B', 8);
foreach ($this->tableHeader as $k => $v) {
$this->Cell($this->tableWidth[$k], 7, $v, 1, 0, 'C', true);
}
$this->SetTextColor(0, 0, 0);
$this->Ln();
}
// ----------------------------------------------------------------
// TABEL DATA BARANG
// ----------------------------------------------------------------
public function ItemsTable($data)
{
/*
* Total lebar kolom = 190mm (pas A4 Portrait, margin 10mm kiri & kanan)
*
* NO : 8
* TGL BELI : 22
* KODE : 22
* NAMA BARANG : 48 ← kolom terpanjang
* GUDANG : 35
* STOK : 13
* HARGA BELI : 21
* HARGA JUAL : 21
* ----
* TOTAL : 190
*/
$w = [8, 22, 22, 48, 35, 13, 21, 21];
$header = [
'NO',
'TGL BELI',
'KODE',
'NAMA BARANG',
'GUDANG',
'STOK',
'HARGA BELI',
'HARGA JUAL',
];
// Daftarkan SEBELUM AddPage() agar Header() sudah tahu isTable=true
// ketika halaman berikutnya dibuat (termasuk halaman ke-2, 3, dst.)
$this->isTable = true;
$this->setTableHeader($header, $w);
// Gambar header tabel di halaman PERTAMA secara eksplisit,
// karena saat AddPage() awal dipanggil isTable masih false.
$this->_drawTableHeader();
// ---- Baris data ----
$this->SetFont('Arial', '', 8);
$no = 1;
foreach ($data as $row) {
// Ganti halaman sebelum batas bawah (A4 Portrait: tinggi efektif ~267mm)
if ($this->GetY() > 262) {
$this->AddPage('P', 'A4');
// _drawTableHeader() sudah dipanggil otomatis oleh Header()
}
// Warna baris selang-seling
if ($no % 2 === 0) {
$this->SetFillColor(255, 245, 230);
$fill = true;
} else {
$fill = false;
}
$this->Cell($w[0], 6, $no++, 1, 0, 'C', $fill);
$this->Cell($w[1], 6, date('d-m-Y', strtotime($row->tanggal_pembelian)), 1, 0, 'C', $fill);
$this->Cell($w[2], 6, $row->kode_detail, 1, 0, 'C', $fill);
$this->Cell($w[3], 6, mb_substr($row->nama_barang, 0, 45), 1, 0, 'L', $fill);
$this->Cell($w[4], 6, mb_substr($row->gudang ?: '-', 0, 30), 1, 0, 'L', $fill);
$this->Cell($w[5], 6, number_format($row->stok), 1, 0, 'R', $fill);
$this->Cell($w[6], 6, number_format($row->harga_beli, 0, ',', '.'), 1, 0, 'R', $fill);
$this->Cell($w[7], 6, number_format($row->harga_jual, 0, ',', '.'), 1, 1, 'R', $fill);
}
$this->isTable = false;
}
// ----------------------------------------------------------------
// RINGKASAN & TANDA TANGAN
// ----------------------------------------------------------------
public function TotalBarang($jumlah)
{
$this->Ln(4);
$this->SetFont('Arial', 'B', 10);
$this->Cell(0, 7, 'Total Data Barang : ' . $jumlah, 0, 1, 'R');
}
// public function Pembuat($nama)
// {
// $this->Ln(12);
// $this->SetFont('Arial', '', 10);
// $this->Cell(0, 6, 'Mengetahui,', 0, 1, 'R');
// $this->Ln(18);
// $this->SetFont('Arial', 'B', 10);
// $this->Cell(0, 6, $nama, 0, 1, 'R');
// }
}
+345
View File
@@ -0,0 +1,345 @@
<?php
require_once(APPPATH . 'third_party/fpdf/fpdf.php');
class PDF extends FPDF
{
// ----------------------------------------------------------------
// PROPERTI HEADER
// ----------------------------------------------------------------
private $nama;
private $alamat;
private $logo;
private $periodeStr = '';
// ----------------------------------------------------------------
// KONFIGURASI TABEL
// ----------------------------------------------------------------
// A4 Portrait efektif = 190mm, margin 10+10
// Kolom: Kode 20 | Nama Akun 120 | Saldo 50 → TOTAL 190
private $colWidths = [20, 120, 50];
// ----------------------------------------------------------------
// SETTER
// ----------------------------------------------------------------
public function setHeaderData($nama, $alamat, $logo = '')
{
$this->nama = $nama;
$this->alamat = $alamat;
$this->logo = $logo;
}
public function setInfoLaporan($periodeStr)
{
$this->periodeStr = $periodeStr;
}
// ----------------------------------------------------------------
// HEADER HALAMAN
// ----------------------------------------------------------------
function Header()
{
// 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 LABA RUGI', 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') . ' Jam ' . date('H:i:s'), 0, 1, 'C');
$this->Ln(4);
// Header kolom tabel
$this->_drawTableHeader();
}
// ----------------------------------------------------------------
// FOOTER HALAMAN
// ----------------------------------------------------------------
function Footer()
{
$this->SetY(-10);
$this->SetFont('Arial', 'I', 8);
$this->Cell(0, 5, 'Halaman ' . $this->PageNo(), 0, 0, 'C');
}
// ----------------------------------------------------------------
// HELPER: posisi X awal — TABEL RATA TENGAH KERTAS
// ----------------------------------------------------------------
private function _startX()
{
$tableW = array_sum($this->colWidths);
return ($this->GetPageWidth() - $tableW) / 2;
}
// ----------------------------------------------------------------
// HELPER: Gambar header kolom tabel
// ----------------------------------------------------------------
private function _drawTableHeader()
{
$startX = $this->_startX();
$this->SetX($startX);
$this->SetFillColor(245, 140, 0);
$this->SetTextColor(255, 255, 255);
$this->SetFont('Arial', 'B', 9);
$this->Cell($this->colWidths[0], 8, 'Kode', 1, 0, 'C', true);
$this->Cell($this->colWidths[1], 8, 'Nama Akun', 1, 0, 'C', true);
$this->Cell($this->colWidths[2], 8, 'Saldo', 1, 1, 'C', true);
$this->SetTextColor(0, 0, 0);
}
// ----------------------------------------------------------------
// HELPER: Gambar baris judul section (PENDAPATAN / BEBAN)
// ----------------------------------------------------------------
private function _drawSectionTitle($title)
{
if ($this->GetY() + 8 > 270) {
$this->AddPage('P', 'A4');
}
$startX = $this->_startX();
$tableW = array_sum($this->colWidths);
$this->SetX($startX);
$this->SetFillColor(255, 224, 178);
$this->SetFont('Arial', 'B', 9);
$this->Cell($tableW, 7, $title, 1, 1, 'L', true);
}
// ----------------------------------------------------------------
// HELPER: Gambar baris detail akun (dengan dukungan multiline Nama Akun)
// ----------------------------------------------------------------
private function _drawRow($kode, $namaAkun, $saldo)
{
$lineH = 6;
$nbLines = $this->NbLines($this->colWidths[1], $namaAkun);
$rowH = max(1, $nbLines) * $lineH;
if ($this->GetY() + $rowH > 270) {
$this->AddPage('P', 'A4');
}
$startX = $this->_startX();
$y = $this->GetY();
$this->SetFont('Arial', '', 9);
$this->SetXY($startX, $y);
$this->Cell($this->colWidths[0], $rowH, $kode, 1, 0, 'C');
$xNama = $this->GetX();
$this->MultiCell($this->colWidths[1], $lineH, $namaAkun, 1, 'L');
$this->SetXY($xNama + $this->colWidths[1], $y);
$this->Cell($this->colWidths[2], $rowH, number_format($saldo, 0, ',', '.'), 1, 0, 'R');
$this->SetXY($startX, $y + $rowH);
}
// ----------------------------------------------------------------
// HELPER: Gambar baris total section
// ----------------------------------------------------------------
private function _drawSectionTotal($label, $total)
{
if ($this->GetY() + 7 > 270) {
$this->AddPage('P', 'A4');
}
$startX = $this->_startX();
$this->SetX($startX);
$this->SetFillColor(255, 224, 178);
$this->SetFont('Arial', 'B', 9);
$this->Cell($this->colWidths[0] + $this->colWidths[1], 7, $label, 1, 0, 'L', true);
$this->Cell($this->colWidths[2], 7, number_format($total, 0, ',', '.'), 1, 1, 'R', true);
}
// ----------------------------------------------------------------
// TABEL LABA RUGI (PENDAPATAN, BEBAN, masing-masing dengan rincian akun)
// ----------------------------------------------------------------
public function LabaRugiTable($pendapatan, $beban, $totalPendapatan, $totalBeban)
{
// ---- SECTION PENDAPATAN ----
$this->_drawSectionTitle('PENDAPATAN');
if (empty($pendapatan)) {
$this->_drawRow('-', 'Tidak ada data', 0);
} else {
foreach ($pendapatan as $row) {
$this->_drawRow($row['kode'], $row['nama'], $row['saldo']);
}
}
$this->_drawSectionTotal('TOTAL PENDAPATAN', $totalPendapatan);
// ---- SECTION BEBAN ----
$this->AddPage('P', 'A4');
$this->_drawSectionTitle('BEBAN');
if (empty($beban)) {
$this->_drawRow('-', 'Tidak ada data', 0);
} else {
foreach ($beban as $row) {
$this->_drawRow($row['kode'], $row['nama'], $row['saldo']);
}
}
$this->_drawSectionTotal('TOTAL BEBAN', $totalBeban);
}
// ----------------------------------------------------------------
// RINGKASAN: LABA / RUGI BERSIH
// ----------------------------------------------------------------
public function LabaRugiRingkasan($laba)
{
if ($this->GetY() + 9 > 270) {
$this->AddPage('P', 'A4');
}
$startX = $this->_startX();
$tableW = array_sum($this->colWidths);
$labelW = $tableW - $this->colWidths[2];
$this->Ln(2);
$this->SetX($startX);
$this->SetFont('Arial', 'B', 10);
if ($laba >= 0) {
$this->SetFillColor(198, 239, 206); // hijau, laba
$label = 'LABA BERSIH';
} else {
$this->SetFillColor(255, 199, 206); // merah, rugi
$label = 'RUGI BERSIH';
}
$this->Cell($labelW, 8, $label, 1, 0, 'L', true);
$this->Cell($this->colWidths[2], 8, number_format($laba, 0, ',', '.'), 1, 1, 'R', true);
}
// ----------------------------------------------------------------
// TANDA TANGAN
// ----------------------------------------------------------------
public function Pembuat($nama, $jabatan = '')
{
$startX = $this->_startX();
$tableW = array_sum($this->colWidths);
$signWidth = 70;
$signX = $startX + $tableW - $signWidth; // sejajar sisi kanan tabel
$this->Ln(10);
$this->SetX($signX);
$this->SetFont('Arial', '', 10);
$this->Cell($signWidth, 6, 'Mengetahui,', 0, 1, 'R');
$this->Ln(10);
$this->SetX($signX);
$this->SetFont('Arial', 'B', 10);
$this->Cell($signWidth, 6, $nama, 0, 1, 'R');
if (!empty($jabatan)) {
$this->SetX($signX);
$this->SetFont('Arial', '', 9);
$this->Cell($signWidth, 5, '(' . $jabatan . ')', 0, 1, 'R');
}
}
// ----------------------------------------------------------------
// HELPER: Hitung jumlah baris yang dibutuhkan MultiCell
// ----------------------------------------------------------------
private function NbLines($w, $txt)
{
$cw = &$this->CurrentFont['cw'];
if ($w == 0) {
$w = $this->w - $this->rMargin - $this->x;
}
$wmax = ($w - 2 * $this->cMargin) * 1000 / $this->FontSize;
$s = str_replace("\r", '', $txt);
$nb = strlen($s);
if ($nb > 0 && $s[$nb - 1] == "\n") {
$nb--;
}
$sep = -1;
$i = 0;
$j = 0;
$l = 0;
$nl = 1;
while ($i < $nb) {
$c = $s[$i];
if ($c == "\n") {
$i++;
$sep = -1;
$j = $i;
$l = 0;
$nl++;
continue;
}
if ($c == ' ') {
$sep = $i;
}
$l += isset($cw[ord($c)]) ? $cw[ord($c)] : 600;
if ($l > $wmax) {
if ($sep == -1) {
if ($i == $j) {
$i++;
}
} else {
$i = $sep + 1;
}
$sep = -1;
$j = $i;
$l = 0;
$nl++;
} else {
$i++;
}
}
return $nl;
}
}
+296
View File
@@ -0,0 +1,296 @@
<?php
require_once(APPPATH . 'third_party/fpdf/fpdf.php');
class PDF extends FPDF
{
private $nama;
private $alamat;
private $logo;
private $periodeStr = '';
private $colWidths = [15, 47, 23];
private $gap = 10;
public function setHeaderData($nama, $alamat, $logo = '')
{
$this->nama = $nama;
$this->alamat = $alamat;
$this->logo = $logo;
}
public function setInfoLaporan($periodeStr)
{
$this->periodeStr = $periodeStr;
}
function Header()
{
if (!empty($this->logo) && file_exists($this->logo)) {
$this->Image($this->logo, 10, 6, 18);
$this->SetXY(31, 10);
} else {
$this->SetXY(10, 10);
}
$this->SetFont('Arial', 'B', 12);
$this->Cell(0, 6, $this->nama, 0, 1, 'L');
$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');
$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);
$this->SetFont('Arial', 'B', 13);
$this->SetTextColor(245, 140, 0);
$this->Cell(0, 7, 'LAPORAN NERACA', 0, 1, 'C');
$this->SetTextColor(0, 0, 0);
$this->SetFont('Arial', '', 10);
$this->Cell(0, 5, $this->periodeStr, 0, 1, 'C');
$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);
$this->_drawSectionHeader();
}
function Footer()
{
$this->SetY(-10);
$this->SetFont('Arial', 'I', 8);
$this->Cell(0, 5, 'Halaman ' . $this->PageNo(), 0, 0, 'C');
}
private function _drawSectionHeader()
{
$startX = $this->_startX();
$tableW = array_sum($this->colWidths);
$this->SetX($startX);
$this->SetFillColor(245, 140, 0);
$this->SetTextColor(255, 255, 255);
$this->SetFont('Arial', 'B', 10);
$this->Cell($tableW, 7, 'ACTIVA', 1, 0, 'C', true);
$this->SetX($startX + $tableW + $this->gap);
$this->Cell($tableW, 7, 'PASIVA', 1, 1, 'C', true);
$this->SetFont('Arial', 'B', 8);
$this->SetFillColor(255, 224, 178);
$this->SetTextColor(0, 0, 0);
$this->SetX($startX);
$this->Cell($this->colWidths[0], 6, 'Kode', 1, 0, 'C', true);
$this->Cell($this->colWidths[1], 6, 'Nama Akun', 1, 0, 'C', true);
$this->Cell($this->colWidths[2], 6, 'Saldo', 1, 0, 'C', true);
$this->SetX($startX + $tableW + $this->gap);
$this->Cell($this->colWidths[0], 6, 'Kode', 1, 0, 'C', true);
$this->Cell($this->colWidths[1], 6, 'Nama Akun', 1, 0, 'C', true);
$this->Cell($this->colWidths[2], 6, 'Saldo', 1, 1, 'C', true);
}
private function _startX()
{
$totalW = (array_sum($this->colWidths) * 2) + $this->gap;
return ($this->GetPageWidth() - $totalW) / 2;
}
/**
* Hitung jumlah baris yang dibutuhkan MultiCell untuk teks tertentu
* pada lebar kolom $w. Diadaptasi dari contoh resmi FPDF.
*/
private function _nbLines($w, $txt)
{
if ($txt === null || $txt === '') return 1;
$cw = &$this->CurrentFont['cw'];
if ($w == 0) {
$w = $this->w - $this->rMargin - $this->x;
}
$wmax = ($w - 2 * $this->cMargin) * 1000 / $this->FontSize;
$s = str_replace("\r", '', (string)$txt);
$nb = strlen($s);
if ($nb > 0 && $s[$nb - 1] == "\n") $nb--;
$sep = -1; $i = 0; $j = 0; $l = 0; $nl = 1;
while ($i < $nb) {
$c = $s[$i];
if ($c == "\n") { $i++; $sep = -1; $j = $i; $l = 0; $nl++; continue; }
if ($c == ' ') $sep = $i;
$l += isset($cw[$c]) ? $cw[$c] : 0;
if ($l > $wmax) {
if ($sep == -1) {
if ($i == $j) $i++;
} else {
$i = $sep + 1;
}
$sep = -1; $j = $i; $l = 0; $nl++;
} else {
$i++;
}
}
return $nl;
}
/**
* Render satu baris (kiri+kanan) dengan tinggi sama,
* memakai MultiCell untuk kolom Nama Akun.
*/
private function _drawRow($activaRow, $pasivaRow, $startX, $rightX, $lineH)
{
$wNama = $this->colWidths[1];
$nlA = $activaRow ? $this->_nbLines($wNama, $activaRow['nama']) : 1;
$nlB = $pasivaRow ? $this->_nbLines($wNama, $pasivaRow['nama']) : 1;
$nl = max($nlA, $nlB, 1);
$h = $lineH * $nl;
// page break manual agar baris tidak terpotong
if ($this->GetY() + $h > $this->PageBreakTrigger) {
$this->AddPage($this->CurOrientation);
}
$y = $this->GetY();
// ---------- ACTIVA ----------
$this->SetXY($startX, $y);
if ($activaRow) {
// Kode (auto vertical-center karena pakai Cell dengan tinggi $h)
$this->Cell($this->colWidths[0], $h, $activaRow['kode'], 1, 0, 'C');
// Nama (MultiCell) — vertical center manual
$xNama = $this->GetX();
$this->Rect($xNama, $y, $wNama, $h);
$nLines = $this->_nbLines($wNama, $activaRow['nama']);
$offsetY = ($h - $lineH * $nLines) / 2;
$this->SetXY($xNama, $y + $offsetY);
$this->MultiCell($wNama, $lineH, $activaRow['nama'], 0, 'L');
// Saldo
$this->SetXY($xNama + $wNama, $y);
$this->Cell($this->colWidths[2], $h, number_format($activaRow['saldo'], 0, ',', '.'), 1, 0, 'R');
} else {
$this->Cell($this->colWidths[0], $h, '', 1, 0);
$this->Cell($wNama, $h, '', 1, 0);
$this->Cell($this->colWidths[2], $h, '', 1, 0);
}
// ---------- PASIVA ----------
$this->SetXY($rightX, $y);
if ($pasivaRow) {
$this->Cell($this->colWidths[0], $h, $pasivaRow['kode'], 1, 0, 'C');
$xNama = $this->GetX();
$this->Rect($xNama, $y, $wNama, $h);
$nLines = $this->_nbLines($wNama, $pasivaRow['nama']);
$offsetY = ($h - $lineH * $nLines) / 2;
$this->SetXY($xNama, $y + $offsetY);
$this->MultiCell($wNama, $lineH, $pasivaRow['nama'], 0, 'L');
$this->SetXY($xNama + $wNama, $y);
$this->Cell($this->colWidths[2], $h, number_format($pasivaRow['saldo'], 0, ',', '.'), 1, 0, 'R');
} else {
$this->Cell($this->colWidths[0], $h, '', 1, 0);
$this->Cell($wNama, $h, '', 1, 0);
$this->Cell($this->colWidths[2], $h, '', 1, 0);
}
// pindah ke baris berikutnya
$this->SetXY($startX, $y + $h);
}
public function NeracaTable($activa, $pasiva, $totalActiva, $totalPasiva)
{
$startX = $this->_startX();
$tableW = array_sum($this->colWidths);
$rightX = $startX + $tableW + $this->gap;
$lineH = 5;
$this->SetFont('Arial', '', 8);
$maxRow = max(count($activa), count($pasiva));
for ($i = 0; $i < $maxRow; $i++) {
$this->_drawRow(
isset($activa[$i]) ? $activa[$i] : null,
isset($pasiva[$i]) ? $pasiva[$i] : null,
$startX, $rightX, $lineH
);
}
// ---- BARIS TOTAL ----
if ($this->GetY() + 8 > $this->PageBreakTrigger) {
$this->AddPage('P', 'A4');
}
$y = $this->GetY();
$this->SetFont('Arial', 'B', 8);
$this->SetFillColor(255, 204, 102);
$this->SetXY($startX, $y);
$this->Cell($this->colWidths[0] + $this->colWidths[1], 7, 'TOTAL ACTIVA', 1, 0, 'C', true);
$this->Cell($this->colWidths[2], 7, number_format($totalActiva, 0, ',', '.'), 1, 0, 'R', true);
$this->SetXY($rightX, $y);
$this->Cell($this->colWidths[0] + $this->colWidths[1], 7, 'TOTAL PASIVA', 1, 0, 'C', true);
$this->Cell($this->colWidths[2], 7, number_format($totalPasiva, 0, ',', '.'), 1, 1, 'R', true);
}
public function NeracaRingkasan($laba, $selisih)
{
$startX = $this->_startX();
$tableW = array_sum($this->colWidths) * 2 + $this->gap;
$labelW = $tableW - 40;
$this->Ln(4);
$this->SetX($startX);
$this->SetFont('Arial', 'B', 9);
$this->Cell($labelW, 7, 'Laba Rugi Berjalan', 1, 0, 'L');
$this->SetFont('Arial', '', 9);
$this->Cell(40, 7, number_format($laba, 0, ',', '.'), 1, 1, 'R');
$this->SetX($startX);
$this->SetFont('Arial', 'B', 9);
if (abs($selisih) < 1) {
$this->SetFillColor(198, 239, 206);
$label = 'Selisih (Balance)';
} else {
$this->SetFillColor(255, 199, 206);
$label = 'Selisih (Tidak Balance)';
}
$this->Cell($labelW, 7, $label, 1, 0, 'L', true);
$this->Cell(40, 7, number_format($selisih, 0, ',', '.'), 1, 1, 'R', true);
}
public function Pembuat($nama, $jabatan = '')
{
$startX = $this->_startX();
$tableW = array_sum($this->colWidths) * 2 + $this->gap;
$signWidth = 70;
$signX = $startX + $tableW - $signWidth;
$this->Ln(15);
$this->SetX($signX);
$this->SetFont('Arial', '', 10);
$this->Cell($signWidth, 6, 'Mengetahui,', 0, 1, 'R');
$this->Ln(18);
$this->SetX($signX);
$this->SetFont('Arial', 'B', 10);
$this->Cell($signWidth, 6, $nama, 0, 1, 'R');
if (!empty($jabatan)) {
$this->SetX($signX);
$this->SetFont('Arial', '', 9);
$this->Cell($signWidth, 5, '(' . $jabatan . ')', 0, 1, 'R');
}
}
}
+313
View File
@@ -0,0 +1,313 @@
<?php
require_once(APPPATH . 'third_party/fpdf/fpdf.php');
class PDF extends FPDF
{
// ----------------------------------------------------------------
// PROPERTI HEADER
// ----------------------------------------------------------------
private $nama;
private $alamat;
private $logo;
private $periodeStr = '';
// ----------------------------------------------------------------
// KONFIGURASI TABEL
// ----------------------------------------------------------------
// A4 Portrait efektif = 190mm, margin 10+10
// Kolom: Kode 20 | Nama Akun 90 | Debit 40 | Kredit 40 → TOTAL 190
private $colWidths = [20, 90, 40, 40];
// ----------------------------------------------------------------
// SETTER
// ----------------------------------------------------------------
public function setHeaderData($nama, $alamat, $logo = '')
{
$this->nama = $nama;
$this->alamat = $alamat;
$this->logo = $logo;
}
public function setInfoLaporan($periodeStr)
{
$this->periodeStr = $periodeStr;
}
// ----------------------------------------------------------------
// HEADER HALAMAN
// ----------------------------------------------------------------
function Header()
{
// 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 KEUANGAN', 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 {
// Halaman berikutnya tanpa header laporan
$this->SetY(10);
}
// Header kolom tabel tetap tampil di setiap halaman
$this->_drawTableHeader();
}
// ----------------------------------------------------------------
// FOOTER HALAMAN
// ----------------------------------------------------------------
function Footer()
{
$this->SetY(-10);
$this->SetFont('Arial', 'I', 8);
$this->Cell(0, 5, 'Halaman ' . $this->PageNo(), 0, 0, 'C');
}
// ----------------------------------------------------------------
// HELPER: posisi X awal — TABEL RATA TENGAH KERTAS
// ----------------------------------------------------------------
private function _startX()
{
$tableW = array_sum($this->colWidths);
return ($this->GetPageWidth() - $tableW) / 2;
}
// ----------------------------------------------------------------
// HELPER: Gambar header kolom tabel
// ----------------------------------------------------------------
private function _drawTableHeader()
{
$startX = $this->_startX();
$this->SetX($startX);
$this->SetFillColor(245, 140, 0);
$this->SetTextColor(255, 255, 255);
$this->SetFont('Arial', 'B', 9);
$this->Cell($this->colWidths[0], 8, 'Kode', 1, 0, 'C', true);
$this->Cell($this->colWidths[1], 8, 'Nama Akun', 1, 0, 'C', true);
$this->Cell($this->colWidths[2], 8, 'Debit', 1, 0, 'C', true);
$this->Cell($this->colWidths[3], 8, 'Kredit', 1, 1, 'C', true);
$this->SetTextColor(0, 0, 0);
}
// ----------------------------------------------------------------
// TABEL NERACA SALDO (mendukung multiline Nama Akun)
// ----------------------------------------------------------------
public function NeracaSaldoTable($rows, $totalDebit, $totalKredit)
{
$startX = $this->_startX();
$lineH = 6;
$this->SetFont('Arial', '', 9);
if (empty($rows)) {
$this->SetX($startX);
$this->SetFont('Arial', 'B', 10);
$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], $namaAkun);
$rowH = max(1, $nbLines) * $lineH;
if ($this->GetY() + $rowH > 270) {
$this->AddPage('P', 'A4');
}
$y = $this->GetY();
$this->SetFont('Arial', '', 9);
$this->SetXY($startX, $y);
$this->Cell($this->colWidths[0], $rowH, $kodeAkun, 1, 0, 'C');
$xNama = $this->GetX();
$this->MultiCell($this->colWidths[1], $lineH, $namaAkun, 1, 'L');
$this->SetXY($xNama + $this->colWidths[1], $y);
$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);
}
}
// ---- BARIS TOTAL ----
if ($this->GetY() + 8 > 270) {
$this->AddPage('P', 'A4');
}
$this->SetX($startX);
$this->SetFillColor(255, 204, 102);
$this->SetFont('Arial', 'B', 9);
$this->Cell($this->colWidths[0] + $this->colWidths[1], 8, 'TOTAL', 1, 0, 'C', true);
$this->Cell($this->colWidths[2], 8, number_format($totalDebit, 0, ',', '.'), 1, 0, 'R', true);
$this->Cell($this->colWidths[3], 8, number_format($totalKredit, 0, ',', '.'), 1, 1, 'R', true);
}
// ----------------------------------------------------------------
// RINGKASAN: STATUS BALANCE
// ----------------------------------------------------------------
public function NeracaSaldoRingkasan($totalDebit, $totalKredit)
{
if ($this->GetY() + 9 > 270) {
$this->AddPage('P', 'A4');
}
$startX = $this->_startX();
$tableW = array_sum($this->colWidths);
$labelW = $tableW - $this->colWidths[3];
$selisih = $totalDebit - $totalKredit;
$this->Ln(2);
$this->SetX($startX);
$this->SetFont('Arial', 'B', 10);
if (abs($selisih) < 1) {
$this->SetFillColor(198, 239, 206); // hijau, balance
$label = 'SELISIH (BALANCE)';
} else {
$this->SetFillColor(255, 199, 206); // merah, tidak balance
$label = 'SELISIH (TIDAK BALANCE)';
}
$this->Cell($labelW, 8, $label, 1, 0, 'L', true);
$this->Cell($this->colWidths[3], 8, number_format($selisih, 0, ',', '.'), 1, 1, 'R', true);
}
// ----------------------------------------------------------------
// TANDA TANGAN
// ----------------------------------------------------------------
public function Pembuat($nama, $jabatan = '')
{
$startX = $this->_startX();
$tableW = array_sum($this->colWidths);
$signWidth = 70;
$signX = $startX + $tableW - $signWidth; // sejajar sisi kanan tabel
$this->Ln(3);
// $this->SetX($signX);
$this->SetFont('Arial', 'I', 9);
$this->Cell($signWidth, 6, '*Mengetahui, (Finance Departement)', 0, 1, 'L');
// $this->Cell($signWidth, 6, $nama, 0, 1, 'L');
// if (!empty($jabatan)) {
// $this->Cell($signWidth, 5, '(' . $jabatan . ')', 0, 1, 'L');
// }
// $this->Ln(0);
// $this->SetX($signX);
// $this->SetFont('Arial', 'B', 10);
// $this->Cell($signWidth, 6, $nama, 0, 1, 'R');
// if (!empty($jabatan)) {
// $this->SetX($signX);
// $this->SetFont('Arial', '', 9);
// $this->Cell($signWidth, 5, '(' . $jabatan . ')', 0, 1, 'R');
// }
}
// ----------------------------------------------------------------
// HELPER: Hitung jumlah baris yang dibutuhkan MultiCell
// ----------------------------------------------------------------
private function NbLines($w, $txt)
{
$cw = &$this->CurrentFont['cw'];
if ($w == 0) {
$w = $this->w - $this->rMargin - $this->x;
}
$wmax = ($w - 2 * $this->cMargin) * 1000 / $this->FontSize;
$s = str_replace("\r", '', $txt);
$nb = strlen($s);
if ($nb > 0 && $s[$nb - 1] == "\n") {
$nb--;
}
$sep = -1;
$i = 0;
$j = 0;
$l = 0;
$nl = 1;
while ($i < $nb) {
$c = $s[$i];
if ($c == "\n") {
$i++;
$sep = -1;
$j = $i;
$l = 0;
$nl++;
continue;
}
if ($c == ' ') {
$sep = $i;
}
$l += isset($cw[ord($c)]) ? $cw[ord($c)] : 600;
if ($l > $wmax) {
if ($sep == -1) {
if ($i == $j) {
$i++;
}
} else {
$i = $sep + 1;
}
$sep = -1;
$j = $i;
$l = 0;
$nl++;
} else {
$i++;
}
}
return $nl;
}
}
@@ -0,0 +1,556 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class PeralatanTeknisi_model extends CI_Model {
public function __construct()
{
parent::__construct();
}
public function get_teknisi_list()
{
return $this->db
->select('e.id, e.employee_code, e.full_name, p.position_name, d.department_name')
->from('k_employees e')
->join('k_positions p', 'p.id = e.position_id', 'left')
->join('k_departments d', 'd.id = e.department_id', 'left')
->where('(e.resign_date IS NULL OR e.resign_date = \'0000-00-00\')')
->where_in('LOWER(p.position_name)', ['teknisi', 'noc'])
->order_by('e.full_name', 'ASC')
->get()
->result();
}
public function get_barang_dibawa($user_id = null, $status = 'active')
{
$this->db
->select("
it.barcode,
it.user_id,
it.item_id,
it.status,
it.created_at,
ib.serial_number,
i.nama_barang,
i.kode_detail,
ke.full_name as teknisi_name,
ke.employee_code
")
->from('item_technician it')
->join('item_barcodes ib', 'ib.barcode = it.barcode', 'left')
->join('items i', 'i.id = it.item_id', 'left')
->join('k_employees ke', 'ke.id = it.user_id', 'left');
if ($user_id) {
$this->db->where('it.user_id', $user_id);
}
if ($status && in_array($status, ['active', 'inactive'])) {
$this->db->where('it.status', $status);
}
$this->db->order_by('it.created_at', 'DESC');
return $this->db->get()->result();
}
public function get_item_by_barcode($barcode)
{
$barcode = trim($barcode);
if ($barcode == '') return null;
$row = $this->db
->select('ib.id as barcode_id, ib.item_id, ib.barcode, ib.serial_number, ib.qty_sisa, ib.status as barcode_status, i.nama_barang, i.kode_detail')
->from('item_barcodes ib')
->join('items i', 'i.id = ib.item_id', 'left')
->where('ib.barcode', $barcode)
->where('ib.qty_sisa >', 0)
->get()
->row();
if (!$row) {
$row = $this->db
->select('ib.id as barcode_id, ib.item_id, ib.barcode, ib.serial_number, ib.qty_sisa, ib.status as barcode_status, i.nama_barang, i.kode_detail')
->from('item_barcodes ib')
->join('items i', 'i.id = ib.item_id', 'left')
->where('ib.serial_number', $barcode)
->where('ib.qty_sisa >', 0)
->get()
->row();
}
return $row;
}
public function get_current_teknisi($barcode)
{
return $this->db
->select('it.user_id, ke.full_name, ke.employee_code')
->from('item_technician it')
->join('k_employees ke', 'ke.id = it.user_id', 'left')
->where('it.barcode', $barcode)
->where('it.status', 'active')
->get()
->row();
}
public function batch_kembalikan(array $barcodes)
{
$now = date('Y-m-d H:i:s');
$errors = [];
$this->db->trans_start();
foreach ($barcodes as $barcode) {
$barcode = trim($barcode);
if ($barcode == '') continue;
$item = $this->db
->select('it.*, ib.id as barcode_id, tec.full_name as teknisi_name')
->from('item_technician it')
->join('item_barcodes ib', 'ib.barcode = it.barcode', 'left')
->join('k_employees tec', 'tec.id = it.user_id', 'left')
->where('it.barcode', $barcode)
->where('it.status', 'active')
->get()
->row();
if (!$item) {
$errors[] = "Barcode $barcode tidak ditemukan atau sudah dikembalikan";
continue;
}
$this->db->insert('item_movements', [
'item_id' => $item->item_id,
'barcode_id' => $item->barcode_id,
'qty' => 1,
'from_type' => 'technician',
'from_id' => $item->user_id,
'to_type' => 'warehouse',
'to_id' => null,
'movement_type' => 'transfer',
'notes' => 'Pengembalian dari: ' . $item->teknisi_name . ' ke gudang',
'created_at' => $now
]);
$this->db
->where('barcode', $barcode)
->where('status', 'active')
->update('item_technician', [
'status' => 'inactive',
'deleted_at' => $now
]);
if ($item->barcode_id) {
$this->db
->where('id', $item->barcode_id)
->update('item_barcodes', [
'status' => 'available'
]);
}
}
$this->db->trans_complete();
if ($this->db->trans_status() === FALSE) {
return ['status' => false, 'message' => 'Gagal memproses pengembalian'];
}
$success_count = count($barcodes) - count($errors);
$msg = "$success_count barang berhasil dikembalikan";
if (count($errors) > 0) {
$msg .= '. ' . implode(', ', $errors);
}
return ['status' => true, 'message' => $msg, 'errors' => $errors];
}
public function get_dates_by_employee_id($user_id)
{
return $this->db
->select("DATE(it.created_at) as tanggal")
->from('item_technician it')
->where('it.user_id', $user_id)
->where('it.status', 'active')
->group_by("DATE(it.created_at)")
->order_by("DATE(it.created_at)", "DESC")
->get()
->result();
}
public function batch_dibawa(array $barcodes, $user_id)
{
$now = date('Y-m-d H:i:s');
$errors = [];
$skipped = 0;
$technician = $this->db
->select('id, full_name, employee_code')
->from('k_employees')
->where('id', $user_id)
->get()
->row();
$this->db->trans_start();
foreach ($barcodes as $barcode) {
$barcode = trim($barcode);
if ($barcode == '') continue;
$barcode_row = $this->db
->select('ib.id as barcode_id, ib.item_id, ib.barcode')
->from('item_barcodes ib')
->where('ib.barcode', $barcode)
->where_not_in('ib.status', ['damaged', 'lost', 'returned_supplier', 'sold_out'])
->get()
->row();
if (!$barcode_row) {
$errors[] = "Barcode $barcode tidak valid atau tidak tersedia";
continue;
}
$already = $this->db
->where('barcode', $barcode_row->barcode)
->where('status', 'active')
->get('item_technician')
->row();
if ($already) {
if ($already->user_id == $user_id) {
$skipped++;
continue;
}
$errors[] = "Barang $barcode sedang dibawa teknisi lain";
continue;
}
$this->db->insert('item_technician', [
'user_id' => $user_id,
'barcode' => $barcode_row->barcode,
'item_id' => $barcode_row->item_id,
'status' => 'active',
'created_at' => $now
]);
$last_movement = $this->db
->where('barcode_id', $barcode_row->barcode_id)
->order_by('id', 'DESC')
->limit(1)
->get('item_movements')
->row();
$from_type = 'warehouse';
$from_id = null;
if ($last_movement) {
$from_type = $last_movement->to_type;
$from_id = $last_movement->to_id;
}
$this->db->insert('item_movements', [
'item_id' => $barcode_row->item_id,
'barcode_id' => $barcode_row->barcode_id,
'qty' => 1,
'from_type' => $from_type,
'from_id' => $from_id,
'to_type' => 'technician',
'to_id' => $user_id,
'movement_type' => 'transfer',
'notes' => 'Dibawa oleh: ' . $technician->full_name . ' dari gudang',
'created_at' => $now
]);
$this->db
->where('id', $barcode_row->barcode_id)
->update('item_barcodes', [
'status' => 'installed'
]);
}
$this->db->trans_complete();
if ($this->db->trans_status() === FALSE) {
return ['status' => false, 'message' => 'Gagal memproses penugasan barang'];
}
$success_count = count($barcodes) - count($errors) - $skipped;
$msg = "$success_count barang berhasil ditugaskan";
if ($skipped > 0) {
$msg .= ". $skipped barang sudah ditugaskan ke teknisi yang sama";
}
if (count($errors) > 0) {
$msg .= '. ' . implode(', ', $errors);
}
return ['status' => true, 'message' => $msg, 'errors' => $errors];
}
public function get_teknisi_table_data($start, $length, $search, $order_col, $order_dir)
{
$sort_cols = [
1 => 'e.full_name',
2 => 'e.employee_code',
3 => 'd.department_name',
4 => 'item_count'
];
$this->db
->select("
e.id,
e.full_name,
e.employee_code,
p.position_name,
d.department_name,
(SELECT COUNT(*) FROM item_technician it WHERE it.user_id = e.id AND it.status = 'active') as item_count
")
->from('k_employees e')
->join('k_positions p', 'p.id = e.position_id', 'left')
->join('k_departments d', 'd.id = e.department_id', 'left')
->where('(e.resign_date IS NULL OR e.resign_date = \'0000-00-00\')')
->where_in('LOWER(p.position_name)', ['teknisi', 'noc']);
if (!empty($search)) {
$this->db->group_start()
->like('LOWER(e.full_name)', strtolower($search))
->or_like('LOWER(e.employee_code)', strtolower($search))
->or_like('LOWER(d.department_name)', strtolower($search))
->group_end();
}
if (isset($sort_cols[$order_col])) {
$this->db->order_by($sort_cols[$order_col], $order_dir);
} else {
$this->db->order_by('e.full_name', 'ASC');
}
if ($length > 0) {
$this->db->limit($length, $start);
}
return $this->db->get()->result();
}
public function count_all_teknisi()
{
$this->db
->from('k_employees e')
->join('k_positions p', 'p.id = e.position_id', 'left')
->where('(e.resign_date IS NULL OR e.resign_date = \'0000-00-00\')')
->where_in('LOWER(p.position_name)', ['teknisi', 'noc']);
return $this->db->count_all_results();
}
public function count_filtered_teknisi($search)
{
$this->db
->from('k_employees e')
->join('k_positions p', 'p.id = e.position_id', 'left')
->join('k_departments d', 'd.id = e.department_id', 'left')
->where('(e.resign_date IS NULL OR e.resign_date = \'0000-00-00\')')
->where_in('LOWER(p.position_name)', ['teknisi', 'noc']);
if (!empty($search)) {
$this->db->group_start()
->like('LOWER(e.full_name)', strtolower($search))
->or_like('LOWER(e.employee_code)', strtolower($search))
->or_like('LOWER(d.department_name)', strtolower($search))
->group_end();
}
return $this->db->count_all_results();
}
public function get_teknisi_info($user_id)
{
return $this->db
->select('e.id, e.full_name, e.employee_code, p.position_name, d.department_name')
->from('k_employees e')
->join('k_positions p', 'p.id = e.position_id', 'left')
->join('k_departments d', 'd.id = e.department_id', 'left')
->where('e.id', $user_id)
->get()
->row();
}
public function get_teknisi_items_data($user_id, $start, $length, $search, $order_col, $order_dir)
{
$sort_cols = [
1 => 'it.created_at',
2 => 'it.barcode',
3 => 'i.nama_barang',
4 => 'ib.serial_number',
5 => 'it.status'
];
$this->db
->select("
it.barcode,
i.nama_barang,
ib.serial_number,
ib.qty_sisa,
it.status,
DATE_FORMAT(it.created_at, '%d/%m/%Y') as tanggal
")
->from('item_technician it')
->join('items i', 'i.id = it.item_id', 'left')
->join('item_barcodes ib', 'ib.barcode = it.barcode', 'left')
->where('it.user_id', $user_id)
->where('it.status', 'active');
if (!empty($search)) {
$this->db->group_start()
->like('it.barcode', $search)
->or_like('LOWER(i.nama_barang)', strtolower($search))
->or_like('ib.serial_number', $search)
->group_end();
}
if (isset($sort_cols[$order_col])) {
$this->db->order_by($sort_cols[$order_col], $order_dir);
} else {
$this->db->order_by('it.created_at', 'DESC');
}
if ($length > 0) {
$this->db->limit($length, $start);
}
return $this->db->get()->result();
}
public function count_all_teknisi_items($user_id)
{
$this->db
->from('item_technician it')
->where('it.user_id', $user_id)
->where('it.status', 'active');
return $this->db->count_all_results();
}
public function count_filtered_teknisi_items($user_id, $search)
{
$this->db
->from('item_technician it')
->join('items i', 'i.id = it.item_id', 'left')
->join('item_barcodes ib', 'ib.barcode = it.barcode', 'left')
->where('it.user_id', $user_id)
->where('it.status', 'active');
if (!empty($search)) {
$this->db->group_start()
->like('it.barcode', $search)
->or_like('LOWER(i.nama_barang)', strtolower($search))
->or_like('ib.serial_number', $search)
->group_end();
}
return $this->db->count_all_results();
}
public function get_transfer_data()
{
return $this->db
->select("
it.barcode,
i.nama_barang,
ib.serial_number,
from_ke.full_name as from_name,
from_ke.employee_code as from_code,
to_ke.full_name as to_name,
to_ke.employee_code as to_code,
im.created_at,
im.movement_type
")
->from('item_movements im')
->join('items i', 'i.id = im.item_id', 'left')
->join('item_barcodes ib', 'ib.id = im.barcode_id', 'left')
->join('k_employees from_ke', 'from_ke.id = im.from_id', 'left')
->join('k_employees to_ke', 'to_ke.id = im.to_id', 'left')
->where('im.movement_type', 'installation')
->where('im.from_type', 'technician')
->where('im.to_type', 'technician')
->order_by('im.created_at', 'DESC')
->limit(200)
->get()
->result();
}
public function detail_by_barcode($barcode)
{
return $this->db
->select("
it.*,
ib.serial_number,
ib.qty_sisa,
ib.status as barcode_status,
i.nama_barang,
i.kode_detail,
ke.full_name as teknisi_name,
ke.employee_code
")
->from('item_technician it')
->join('item_barcodes ib', 'ib.barcode = it.barcode', 'left')
->join('items i', 'i.id = it.item_id', 'left')
->join('k_employees ke', 'ke.id = it.user_id', 'left')
->where('it.barcode', $barcode)
->get()
->row();
}
public function get_teknisi_items_full($user_id, $tanggal = null)
{
$this->db
->select("
it.barcode,
i.nama_barang,
ib.serial_number,
ib.qty_sisa,
it.status,
DATE_FORMAT(it.created_at, '%d/%m/%Y') as tanggal
")
->from('item_technician it')
->join('items i', 'i.id = it.item_id', 'left')
->join('item_barcodes ib', 'ib.barcode = it.barcode', 'left')
->where('it.user_id', $user_id)
->where('it.status', 'active');
if ($tanggal AND $tanggal != 'all') {
$this->db->where('DATE(it.created_at)', $tanggal);
}
return $this->db
->order_by('it.created_at', 'ASC')
->get()
->result();
}
public function generate_no_faktur()
{
return 'GRJN/K/' . date('d/m/Y');
}
public function get_log_barang()
{
return $this->db
->select("
it.barcode,
it.item_id,
it.user_id,
it.status,
it.created_at,
it.deleted_at,
ke.full_name as teknisi_name,
i.kode_detail as item_code,
i.nama_barang
")
->from('item_technician it')
->join('k_employees ke', 'ke.id = it.user_id', 'left')
->join('items i', 'i.id = it.item_id', 'left')
->order_by('it.created_at', 'DESC')
->get()
->result();
}
}
+308 -38
View File
@@ -16,9 +16,10 @@
<tr>
<th>No</th>
<th>Tanggal</th>
<th>Kode</th>
<th>Nama</th>
<th style="min-width: 80px;">Kode</th>
<th style="min-width: 80px;">Nama</th>
<th>Lokasi Asset</th>
<th style="max-width: 150px;">Keterangan</th>
<th>Nilai</th>
<th>Masa</th>
<th>Susut/Bulan</th>
@@ -56,9 +57,10 @@
<input type="text" id="nama_asset" class="form-control">
<label class="mt-2">Keterangan</label>
<input type="text" id="keterangan" class="form-control">
<textarea rows="3" id="keterangan" class="form-control"></textarea>
<!-- <input type="text" id="keterangan" class="form-control"> -->
<label class="mt-2">Harga Per Unit</label>
<label class="mt-2">Harga</label>
<input type="text" id="harga" class="form-control format-rupiah">
<label class="mt-2">Tanggal</label>
@@ -99,7 +101,8 @@
<select id="edit_lokasi_asset" class="form-control"></select>
<label class="mt-2">Keterangan</label>
<input type="text" id="edit_keterangan" class="form-control">
<!-- <input type="text" id="edit_keterangan" class="form-control"> -->
<textarea rows="3" id="edit_keterangan" class="form-control"></textarea>
</div>
<div class="modal-footer">
<button class="btn btn-primary" id="btnUpdate">Update</button>
@@ -146,7 +149,7 @@
</div>
<div class="modal-body">
<input type="hidden" id="s_item_id">
<input type="hidden" id="s_harga">
<!-- <input type="hidden" id="s_harga"> -->
<label class="mt-2">Akun Asset (Debit)</label>
<select id="s_account_asset" class="form-control"></select>
@@ -155,14 +158,33 @@
<select id="s_lokasi_asset" class="form-control"></select>
<label>Pilih Barang</label>
<select id="s_item" class="form-control"></select>
<select id="s_item" class="form-control select-search"></select>
<label class="mt-2">Stok Tersedia</label>
<input type="text" id="s_stok" class="form-control" readonly>
<div class="mb-2 d-none" id="wrap_barcode_select">
<label for="barcode_select" class="form-label">Barcode / QR ID</label>
<select id="barcode_select" class="form-control select-search"></select>
</div>
<div id="stokSisaQty"></div>
<label class="mt-2">Harga Barang</label>
<input type="text" id="s_harga" class="form-control format-rupiah" disabled>
<div class="mb-0 d-none" id="wrap_qty">
<label class="mt-2">Qty</label>
<input type="number" id="s_qty" placeholder="1" class="form-control">
<label class="mt-2">Total Harga</label>
<input type="text" id="s_total_harga" class="form-control format-rupiah" disabled>
</div>
<label class="mt-2">Tanggal</label>
<input type="date" id="s_tanggal" class="form-control">
<label class="mt-2">Keterangan</label>
<textarea rows="3" id="s_keterangan" class="form-control"></textarea>
<!-- <input type="text" id="s_keterangan" class="form-control"> -->
<hr>
<label>Masa Manfaat (bulan)</label>
@@ -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='<option value="">-- PILIH BARANG --</option>';
res.forEach(i=>{
opt += `<option value="${i.id}" data-harga="${i.harga_beli}" data-stok="${i.stok}" data-nama="${i.nama_barang}">
opt += `<option value="${i.id}" data-id="${i.id}" data-harga="${i.harga_beli}" data-stok="${i.stok}" data-nama="${i.nama_barang}" data-type="${i.tracking_type}">
${i.kode_detail} - ${i.nama_barang} - ( Stok: ${i.stok})
</option>`;
});
@@ -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 = '<option value="">Pilih Item</option>';
// res.forEach(i=>{
// opt += `<option value="${i.barcode_id}">
// ${i.nama_barang} - ${i.barcode} - ${i.serial_number}
// </option>`;
// });
// $('#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 = '<option value="">Pilih Barcode</option>';
res.forEach(i=>{
opt += `
<option value="${i.barcode_id}" data-qty_sisa="${i.qty_sisa}">
${i.nama_barang} - ${i.barcode} - ${i.serial_number}
</option>
`;
});
$('#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(`
<table class="table table-borderless">
<tr><td><strong>Kode Asset</strong></td><td>: <strong>${asset.kode_asset || '-'}</strong></td></tr>
<tr><td><strong>Nama Asset</strong></td><td>: <strong>${asset.nama_asset}</strong></td></tr>
<tr><td><strong>Lokasi</strong></td><td>: <strong>${asset.nama_lokasi || '-'}</strong></td></tr>
<tr><td><strong>Harga/Unit</strong></td><td>: <strong>Rp ${formatRupiah(asset.harga_per_unit)}</strong></td></tr>
<tr><td><strong>Nilai Buku</strong></td><td>: <strong>Rp ${formatRupiah(asset.nilai_buku)}</strong></td></tr>
<tr><td><strong>Masa Manfaat</strong></td><td>: <strong>${asset.masa_manfaat} bulan</strong></td></tr>
<tr><td><strong>Sumber</strong></td><td>: <strong>${asset.sumber}</strong></td></tr>
<tr><td><strong>Keterangan</strong></td><td>: <strong>${asset.keterangan || '-'}</strong></td></tr>
</table>
<div class="row">
<div class="col-md-6">
<table class="table table-borderless table-sm">
<tr>
<td width="45%"><strong>Kode Asset</strong></td>
<td>: <strong>${asset.kode_asset || '-'}</strong></td>
</tr>
<tr>
<td><strong>Nama Asset</strong></td>
<td>: <strong>${asset.nama_asset}</strong></td>
</tr>
<tr>
<td><strong>Lokasi</strong></td>
<td>: <strong>${asset.nama_lokasi || '-'}</strong></td>
</tr>
<tr>
<td><strong>Harga/Unit</strong></td>
<td>: <strong>Rp ${formatRupiah(asset.harga_per_unit)}</strong></td>
</tr>
<tr>
<td><strong>Qty</strong></td>
<td>: <strong>${asset.qty}</strong></td>
</tr>
<tr>
<td><strong>Nilai Perolehan</strong></td>
<td>: <strong>Rp ${formatRupiah(asset.nilai_perolehan)}</strong></td>
</tr>
</table>
</div>
<div class="col-md-6">
<table class="table table-borderless table-sm">
<tr>
<td width="45%"><strong>Akumulasi Penyusutan</strong></td>
<td>: <strong>Rp ${formatRupiah(asset.akumulasi_penyusutan || 0)}</strong></td>
</tr>
<tr>
<td><strong>Nilai Buku</strong></td>
<td>: <strong>Rp ${formatRupiah(asset.nilai_buku)}</strong></td>
</tr>
<tr>
<td><strong>Masa Manfaat</strong></td>
<td>: <strong>${asset.masa_manfaat} bulan</strong></td>
</tr>
<tr>
<td><strong>Sumber</strong></td>
<td>: <strong>${asset.sumber}</strong></td>
</tr>
<tr>
<td><strong>Keterangan</strong></td>
<td>: <strong>${asset.keterangan || '-'}</strong></td>
</tr>
</table>
</div>
</div>
`);
// 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 += `<tr>
<td>${h.created_at}</td>
<td>${h.tanggal_dibuat}</td>
<td><span class="badge ${badgeClass}">${h.tipe}</span></td>
<td>Rp ${formatRupiah(h.nilai || 0)}</td>
<td>${h.keterangan}</td>
@@ -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('<option value="">Loading...</option>');
}
$('.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('<h6 class="text-success ml-4">Stok : ' + qty_sisa + '</h6><hr>');
}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('<option value="">Pilih Barcode</option>');
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('<option value="">Barcode tidak tersedia</option>');
}
// 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('<option value="">Gagal load barcode</option>');
$('#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(){
});
});
});
</script>
+58 -3
View File
@@ -13,19 +13,23 @@
<select id="account_id" class="form-control select-search"></select>
</div>
<div class="col-md-3">
<div class="col-md-2">
<label>Dari</label>
<input type="date" id="start_date" class="form-control">
</div>
<div class="col-md-3">
<div class="col-md-2">
<label>Sampai</label>
<input type="date" id="end_date" class="form-control">
</div>
<div class="col-md-2 d-flex align-items-end">
<div class="col-md-4 d-flex align-items-end gap-2">
<button class="btn btn-primary w-100" id="btnFilter">Filter</button>
<button type="button" class="btn btn-danger w-100" id="generate_pdf">
Generate PDF</button>
</div>
</div>
<div class="table-responsive">
@@ -92,10 +96,61 @@ $(function(){
},'json');
}
// FILTER
$('#btnFilter').click(function(){
loadData();
});
// Prevent double submit
$(document).on('submit', 'form', function(e){
e.preventDefault();
});
// GENERATE PDF
$('#generate_pdf').on('click', function () {
const now = new Date();
const today = now.getFullYear() + '-'
+ String(now.getMonth() + 1).padStart(2, '0') + '-'
+ String(now.getDate()).padStart(2, '0');
const account_id = $('#account_id').val();
const start_date = $('#start_date').val() || '0000-00-00';
const end_date = $('#end_date').val() || today;
let btn = $(this);
btn.prop('disabled', true).html('Generate PDF...');
$.ajax({
url: "<?= base_url('Generatebuku/generatepdf'); ?>"
+ '/' + account_id
+ '/' + start_date
+ '/' + end_date,
type: "GET",
xhrFields: {
responseType: 'blob' // penting untuk PDF
},
success: function (response) {
let blob = new Blob([response], { type: 'application/pdf' });
let url = window.URL.createObjectURL(blob);
// buka di tab baru
window.open(url, '_blank');
},
error: function () {
Swal.fire('Error', 'Gagal generate PDF', 'error');
},
complete: function(){
btn.prop('disabled', false).html('Generate PDF');
}
});
});
});
</script>
+125 -76
View File
@@ -89,7 +89,7 @@
<!-- MODAL (TIDAK DIUBAH STYLE) -->
<div class="modal fade" id="modalItem">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content">
<div class="modal-header bg-warning text-white">
<h5 class="modal-title" id="modalTitle">Item Invoice</h5>
@@ -98,9 +98,6 @@
<div class="modal-body">
<input type="hidden" id="item_id">
<div id="item_mode_label" class="alert alert-secondary py-1 small mb-3">
Mode: Manual Input
</div>
<div class="mb-2">
<label for="account_id" class="form-label">Tujuan Neraca (Account)</label>
@@ -124,7 +121,14 @@
<select id="item_select" class="form-control select-search"></select>
</div>
<div class="mb-2">
<div class="mb-2 d-none" id="wrap_barcode_select">
<label for="barcode_select" class="form-label">Barcode / QR ID</label>
<select id="barcode_select" class="form-control select-search"></select>
</div>
<div id="stokSisaQty"></div>
<div class="mb-2" id="wrap_qty_item">
<label for="qty" class="form-label">Qty</label>
<input type="number" id="qty" class="form-control" min="0">
</div>
@@ -225,9 +229,12 @@ $(function(){
// ================= RESET UI =================
function resetForm(){
// $('#form_scan_barang').addClass('d-none');
$('#wrap_warehouse').addClass('d-none');
$('#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('<option value="">Pilih Gudang</option>');
$('#item_select').html('<option value="">Pilih Item</option>');
@@ -237,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');
}
@@ -270,12 +279,15 @@ $(function(){
$.get("<?= base_url('invoices/check_items_by_account/'); ?>" + account_id, function(res){
if(res.has_item){
// $('#form_scan_barang').removeClass('d-none');
$('#wrap_warehouse').removeClass('d-none');
$('#wrap_nama_item').addClass('d-none');
$('#wrap_qty_item').addClass('d-none');
loadWarehouse();
setMode('warehouse');
} else {
$('#wrap_nama_item').removeClass('d-none');
$('#wrap_qty_item').removeClass('d-none');
setMode('manual');
}
}, 'json').fail(function(){
@@ -305,6 +317,7 @@ $(function(){
if(!warehouse_id){
$('#wrap_nama_item').removeClass('d-none');
$('#wrap_qty_item').removeClass('d-none');
setMode('manual');
return;
}
@@ -328,15 +341,116 @@ $(function(){
$('#item_select').html(opt);
$('#wrap_item_select').removeClass('d-none');
$('#wrap_nama_item').addClass('d-none');
$('#wrap_qty_item').addClass('d-none');
setMode('item');
} else {
$('#wrap_item_select').addClass('d-none');
$('#wrap_nama_item').removeClass('d-none');
$('#wrap_qty_item').removeClass('d-none');
setMode('manual');
}
}, 'json');
}
$('#item_select').on('change', function(){
let item_id = $(this).val();
$('#wrap_barcode_select').addClass('d-none');
$('#barcode_select').html('<option value="">Loading...</option>');
if(!item_id){
$('#wrap_barcode_select').removeClass('d-none');
setMode('manual');
return;
}
// 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('<h6 class="text-success ml-4">Stok : ' + qty_sisa + '</h6><hr>');
$('#qty').val('');
$('#wrap_qty_item').removeClass('d-none');
}
});
// $(document).on('keyup','#qty',function(){
// hitungTotal();
// });
// function hitungTotal(){
// let qty_sisa = 1000;
// $('#stokSisaQty').html('<h6 class="text-success ml-4">Stok : ' + qty_sisa + '</h6><hr>');
// }
// ================= 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 = '<option value="">Pilih Item</option>';
// res.forEach(i=>{
// opt += `<option value="${i.barcode_id}">
// ${i.nama_barang} - ${i.barcode} - ${i.serial_number}
// </option>`;
// });
// $('#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 = '<option value="">Pilih Barcode</option>';
res.forEach(i=>{
opt += `
<option value="${i.barcode_id}" data-qty_sisa="${i.qty_sisa}">
${i.nama_barang} - ${i.barcode} - ${i.serial_number}
</option>
`;
});
$('#barcode_select').html(opt);
if(selectedBarcode){
$('#barcode_select').val(selectedBarcode);
}
$('#wrap_barcode_select').removeClass('d-none');
if(!isEditing){
$('#qty').val(1);
}
setMode('item');
}else{
$('#wrap_barcode_select').addClass('d-none');
setMode('manual');
}
},'json');
}
// ================= AUTO HARGA =================
$('#item_select').on('change', function(){
let selected = $(this).find(':selected');
@@ -347,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');
@@ -476,25 +590,7 @@ $(function(){
let btnAction = '';
if(v.status == 'draft'){
let btnEditTable = '';
if(v.is_cicilan == 0) {
btnEditTable = `
<button class="btn btn-sm btn-secondary btn-editItem"
data-id="${v.id}"
data-tanggal_input="${v.tanggal}"
data-account="${v.account_id}"
data-item="${v.nama_item}"
data-qty="${v.qty}"
data-harga="${v.harga}"
data-warehouse="${v.warehouse_id || ''}"
data-item_id="${v.items_id || ''}"
data-keterangan="${v.keterangan || ''}"
data-is_cicilan="${v.is_cicilan || 0}">
Edit
</button>`;
}
btnAction = `
${btnEditTable}
<button class="btn btn-sm btn-danger btn-delete" data-id="${v.id}">
Hapus
</button>
@@ -509,7 +605,7 @@ $(function(){
</td>
<td>${no++}</td>
<td>${formatTglIndo(v.tanggal)}</td>
<td>${v.nama_item || '-'}</td>
<td>${v.nama_item || '-'}<br><small class="text-secondary"><small></small><i>${v.barcode || ''}</i></small></td>
<td>${v.qty || 0}</td>
<td>${formatNumber(v.harga)}</td>
<td>${formatNumber(subtotal)}</td>
@@ -563,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 =================
@@ -642,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,
@@ -706,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){
@@ -1159,6 +1209,5 @@ $(function(){
$(`tr[data-group="${group}"]`).removeClass('table-secondary');
$(this).addClass('table-secondary');
});
</script>
+1 -1
View File
@@ -365,7 +365,7 @@ $('.btn-add-bayar').click(function(){
$('#tableBayar tbody').append(`
<tr>
<td><select class="form-control account_id">${accountOptionsAssets}</select></td>
<td style="width: 60%;"><select class="form-control account_id select-search">${accountOptionsAssets}</select></td>
<td><input type="text" class="form-control debit text-end format-rupiah"></td>
<td><button class="btn btn-danger btn-remove">X</button></td>
</tr>
+641
View File
@@ -0,0 +1,641 @@
<style>
body {
background: #f5f7fb;
}
/* ===========================
CARD
=========================== */
.main-card,
.scanner-card,
.table-card {
border: none;
border-radius: 18px;
overflow: hidden;
/*box-shadow: 0 8px 25px rgba(0, 0, 0, .08);*/
}
/* ===========================
HEADER
=========================== */
.header-gradient {
background: linear-gradient(135deg, #f59e0b, #d97706);
color: #fff;
padding: 22px 28px;
}
.info-box {
background: rgba(255,255,255,.18);
border-radius: 12px;
padding: 12px 16px;
}
.progress {
height: 8px;
border-radius: 20px;
}
/* ===========================
SCANNER
=========================== */
.barcode-avatar {
width: 110px;
height: 110px;
margin: auto;
border-radius: 50%;
background: #fff7e6;
color: #f59e0b;
display: flex;
justify-content: center;
align-items: center;
font-size: 45px;
}
.barcode-code {
font-size: 22px;
font-weight: 700;
letter-spacing: 2px;
}
.form-control-lg {
height: 60px;
font-size: 22px;
border-radius: 12px;
}
.form-control:focus {
border-color: #f59e0b;
box-shadow: 0 0 0 .2rem rgba(245, 158, 11, .2);
}
.scan-note {
color: #6c757d;
font-size: 13px;
}
.camera-btn {
height: 55px;
border-radius: 12px;
font-size: 17px;
}
/* ===========================
TABLE
=========================== */
.table thead th {
background: #d97706;
color: #fff;
border: none;
vertical-align: middle;
}
.table tbody td {
vertical-align: middle;
}
.table tbody tr:hover {
background: #fffaf0;
}
/* ===========================
BADGE
=========================== */
.badge-success-soft {
display: inline-block;
padding: 6px 14px;
border-radius: 30px;
background: #d1fae5;
color: #065f46;
font-weight: 600;
}
.badge-danger-soft {
display: inline-block;
padding: 6px 14px;
border-radius: 30px;
background: #fee2e2;
color: #991b1b;
font-weight: 600;
}
.badge-warning-soft {
display: inline-block;
padding: 6px 14px;
border-radius: 30px;
background: #fef3c7;
color: #92400e;
font-weight: 600;
}
#reader{
width:100%;
min-height:280px;
border-radius:12px;
overflow:hidden;
}
</style>
<div class="container mt-4">
<div class="card main-card">
<!-- HEADER -->
<div class="header-gradient">
<div class="row align-items-center">
<div class="col-md-8">
<h3 class="mb-1">
<i class="fa fa-barcode"></i>
Input Serial Number
</h3>
<div class="small">
Silahkan scan Serial Number perangkat.
Data akan otomatis tersimpan.
</div>
</div>
<div class="col-md-4 text-right">
<div class="info-box">
<div class="small">
Progress
</div>
<h5 class="font-weight-bold mb-2" id="progress_text">
0 / 0 Barcode
</h5>
<div class="progress">
<div class="progress-bar bg-success" style="width:35%"></div>
</div>
</div>
</div>
</div>
</div>
<!-- BODY -->
<div class="card-body">
<div class="row">
<!-- LEFT -->
<div class="col-lg-4 mb-4">
<div class="card scanner-card">
<div class="card-body">
<div class="barcode-avatar">
<i class="bi bi-qr-code-scan"></i>
</div>
<div class="text-center mt-3">
<div class="barcode-code">
ONU-250626-0007
</div>
<div class="scan-note mt-2">
Barcode yang sedang menunggu Serial Number
</div>
</div>
<hr>
<center>
<label class="font-weight-bold">
Serial Number
</label>
<!-- <input
type="text"
id="serial_number"
class="form-control form-control-lg text-center"
placeholder="Scan Serial Number"
autocomplete="off"
autofocus
>
<small class="scan-note">
Scanner akan otomatis menyimpan.
</small>
<button class="btn btn-warning btn-block camera-btn mt-4">
<i class="fa fa-camera mr-2"></i>
Scan dengan Kamera
</button> -->
<div id="scannerArea">
<input
type="text"
id="serial_number"
class="form-control form-control-lg text-center"
placeholder="Scan Serial Number"
autocomplete="off"
autofocus>
<small class="scan-note">
Scanner akan otomatis menyimpan.
</small>
<button
type="button"
class="btn btn-warning btn-block camera-btn mt-4"
id="btnCamera">
<i class="fa fa-camera mr-2"></i>
Scan dengan Kamera
</button>
</div>
<div id="cameraArea" style="display:none;">
<div id="reader"></div>
<small class="scan-note d-block mt-2">
Arahkan QR Code ke kamera.
</small>
<button
type="button"
class="btn btn-secondary btn-block mt-3"
id="btnCloseCamera">
Tutup Kamera
</button>
</div>
</center>
<div class="alert alert-warning mt-3 mb-0 text-center">
<i class="fa fa-check-circle"></i>
Auto Save Aktif
</div>
<input type="hidden" id="barcode_id">
</div>
</div>
</div>
<!-- RIGHT -->
<div class="col-lg-8">
<div class="card table-card">
<div class="card-header bg-white">
<div class="d-flex justify-content-between align-items-center">
<h5 class="mb-0">
Daftar Barcode
</h5>
<span class="badge badge-warning px-3 py-2">
20 Data
</span>
</div>
</div>
<div class="table-responsive">
<table class="table table-hover mb-0" id="tableBarcode">
<thead>
<tr>
<th width="60">No</th>
<th>Barcode</th>
<th>Serial Number</th>
<th width="120">Status</th>
<th width="80" class="text-center">Action</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td>
<strong></strong>
</td>
<td></td>
<td>
<span class="badge-success-soft">
<i class="fa fa-check mr-1"></i>
</span>
</td>
<td class="text-center">
<button class="btn btn-sm btn-outline-danger">
<i class="fa fa-trash"></i>
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
let ITEM_ID = `<?= $item_id; ?>` ;
let isSaving = false;
$(document).ready(function () {
loadData();
$("#serial_number").focus();
$("#serial_number").on("keypress", function (e) {
if (e.which == 13) {
e.preventDefault();
if (!isSaving) {
saveSerial();
}
}
});
});
function loadData() {
$.getJSON( "<?= base_url(); ?>" + "items/load_data_barcode/" + ITEM_ID)
.done(function (r) {
render(r);
})
.fail(function () {
Swal.fire({
icon: 'error',
title: 'Oops...',
text: 'Gagal mengambil data barcode.'
});
});
}
function saveSerial() {
let serial = $("#serial_number").val().trim();
if (serial == "") {
$("#serial_number").focus();
return;
}
isSaving = true;
$("#serial_number")
.prop("disabled", true);
$.ajax({
url: "<?= base_url(); ?>" + "items/save_serial_number",
type: "POST",
dataType: "json",
data: {
barcode_id: $("#barcode_id").val(),
serial_number: serial
},
success: function (r) {
isSaving = false;
render(r);
},
error: function () {
isSaving = false;
$("#serial_number")
.prop("disabled", false)
.focus();
Swal.fire({
icon: "error",
title: "Error",
text: "Terjadi kesalahan pada server."
});
}
});
}
function render(r) {
if (!r.status) {
$("#serial_number")
.val("")
.prop("disabled", false)
.focus();
Swal.fire({
icon: "error",
title: "Oops...",
text: r.message
});
return;
}
// =======================
// Semua SN sudah selesai
// =======================
if (r.next == null) {
$("#barcode_id").val("");
$(".barcode-code").html("SELESAI");
$(".progress-bar").css("width", "100%");
$("#progress_text").html(r.done + " / " + r.total);
if (r.item_status == "draft") {
$("#serial_number")
.val("")
.prop("disabled", false)
.attr("placeholder", "Semua SN selesai. Edit melalui daftar di bawah jika ada yang salah.")
.blur();
Swal.fire({
icon: "success",
title: "Selesai",
text: "Semua Serial Number telah diinput. Karena status barang masih Draft, Anda masih dapat mengubah Serial Number melalui daftar di bawah."
});
} else {
$("#serial_number")
.val("")
.prop("disabled", true);
Swal.fire({
icon: "success",
title: "Selesai",
text: "Semua Serial Number berhasil diinput."
});
}
} else {
$("#barcode_id").val(r.next.id);
$(".barcode-code").text(r.next.barcode);
$("#progress_text").text(r.done + " / " + r.total);
$(".progress-bar").css("width", r.percent + "%");
$("#serial_number")
.val("")
.prop("disabled", false)
.attr("placeholder", "Scan Serial Number")
.focus();
}
// =======================
// Table
// =======================
let html = "";
$.each(r.list, function (i, v) {
html += "<tr>";
html += "<td>" + (i + 1) + "</td>";
html += "<td><strong>" + v.barcode + "</strong></td>";
html += "<td>" + (v.serial_number ? v.serial_number : "-") + "</td>";
html += "<td>";
if (v.serial_number) {
html += '<span class="badge-success-soft"><i class="fa fa-check mr-1"></i>Selesai</span>';
} else {
html += '<span class="badge-warning-soft">Menunggu</span>';
}
html += "</td>";
html += '<td class="text-center">';
if (r.item_status == "draft") {
html += '<button class="btn btn-sm btn-outline-danger btnDelete" data-id="' + v.id + '">Delete</button>';
} else {
html += '<button class="btn btn-sm btn-outline-secondary" disabled>Locked</button>';
}
html += "</td>";
html += "</tr>";
});
$("#tableBarcode tbody").html(html);
}
// function render(r) {
// if (!r.status) {
// $("#serial_number")
// .val("")
// .prop("disabled", false)
// .focus();
// Swal.fire({
// icon: "error",
// title: "Oops...",
// text: r.message
// });
// return;
// }
// if (r.next == null) {
// $("#barcode_id").val("");
// $(".barcode-code").html("SELESAI");
// $("#serial_number")
// .val("")
// .prop("disabled", true);
// $(".progress-bar")
// .css("width", "100%");
// $("#progress_text").html(r.done + " / " + r.total);
// Swal.fire({
// icon: "success",
// title: "Selesai",
// text: "Semua Serial Number berhasil diinput."
// });
// return;
// }
// // Barcode Aktif
// $("#barcode_id").val(r.next.id);
// $(".barcode-code").text(r.next.barcode);
// // Progress
// $("#progress_text").text(r.done + " / " + r.total);
// $(".progress-bar")
// .css("width", r.percent + "%");
// // Table
// let html = "";
// $.each(r.list, function (i, v) {
// html += "<tr>";
// html += "<td>" + (i + 1) + "</td>";
// html += "<td><strong>" + v.barcode + "</strong></td>";
// html += "<td>" + (v.serial_number ? v.serial_number : "-") + "</td>";
// html += "<td>";
// if (v.serial_number) {
// html += '<span class="badge-success-soft"><i class="fa fa-check mr-1"></i>Selesai</span>';
// } else {
// html += '<span class="badge-warning-soft">Menunggu</span>';
// }
// html += "</td>";
// html += '<td class="text-center">';
// html += '<button class="btn btn-sm btn-outline-danger btnDelete" data-id="' + v.id + '">';
// html += 'Delete';
// html += '</button>';
// html += '</td>';
// html += "</tr>";
// });
// $("#tableBarcode tbody").html(html);
// // Reset Input
// $("#serial_number")
// .val("")
// .prop("disabled", false)
// .focus();
// }
$(document).on("click", ".btnDelete", function () {
let id = $(this).data("id");
Swal.fire({
title: "Hapus Serial Number?",
icon: "warning",
showCancelButton: true,
confirmButtonText: "Ya",
cancelButtonText: "Batal"
}).then(function (result) {
if (!result.isConfirmed) return;
$.post(
"<?= base_url(); ?>" + "items/delete_serial_number",
{
barcode_id: id
},
function (r) {
render(r);
},
"json"
);
});
});
let html5QrCode=null;
$("#btnCamera").click(function(){
$("#scannerArea").hide();
$("#cameraArea").show();
html5QrCode=new Html5Qrcode("reader");
html5QrCode.start(
{
facingMode:"environment"
},
{
fps:10,
qrbox:220
},
function(decodedText){
html5QrCode.stop().then(function(){
$("#cameraArea").hide();
$("#scannerArea").show();
$("#serial_number")
.val(decodedText);
saveSerial();
});
}
);
});
$("#btnCloseCamera").click(function(){
if(html5QrCode){
html5QrCode.stop().then(function(){
$("#cameraArea").hide();
$("#scannerArea").show();
$("#serial_number").focus();
});
}
});
</script>
+421 -297
View File
@@ -34,7 +34,7 @@
<!-- MODAL -->
<div class="modal fade" id="modalItem">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-dialog modal-dialog-centered modal-xl">
<div class="modal-content">
<div class="modal-header bg-warning text-white">
@@ -45,39 +45,146 @@
<input type="hidden" id="id">
<div id="group_tanggal">
<label>Tanggal Pembelian</label>
<input type="date" id="tanggal_beli" class="form-control">
</div>
<div id="group_kode">
<label class="mt-2">Kode Barang</label>
<select id="kode_barang" class="form-control select-search"></select>
<div class="row">
<div class="col-md-6">
<div id="group_tanggal">
<label>Tanggal Pembelian</label>
<input type="date" id="tanggal_beli" class="form-control">
</div>
</div>
<div class="col-md-6">
<div id="group_kode">
<label class="mt-2">Kode Barang</label>
<select id="kode_barang" class="form-control select-search"></select>
</div>
</div>
</div>
<label>Nama Barang</label>
<input type="text" id="nama_barang" class="form-control">
<div id="group_qty">
<label class="mt-2">Jumlah Barang</label>
<input type="number" id="qty" class="form-control">
<div class="row">
<div class="col-md-2">
<div id="group_qty">
<label class="mt-2">Jumlah Barang</label>
<input type="number" id="qty" class="form-control">
</div>
</div>
<div class="col-md-2">
<div id="group_tracking_qty">
<label class="mt-2">Jml / (Roll, Box, Dus)</label>
<input type="number" id="tracking_qty" class="form-control">
</div>
</div>
<div class="col-md-4">
<label class="mt-2">Harga Beli</label>
<div class="input-group">
<span class="input-group-text">Rp</span>
<input type="text" id="harga_beli" class="form-control format-rupiah">
</div>
</div>
<div class="col-md-4">
<label class="mt-2">Harga Jual</label>
<div class="input-group">
<span class="input-group-text">Rp</span>
<input type="text" id="harga_jual" class="form-control format-rupiah">
</div>
</div>
</div>
<label class="mt-2">Harga Beli</label>
<input type="text" id="harga_beli" class="form-control format-rupiah">
<label class="mt-2">Harga Jual</label>
<input type="text" id="harga_jual" class="form-control format-rupiah">
<div id="group_warehouse">
<label class="mt-2">Gudang</label>
<select id="warehouse_id" class="form-control"></select>
</div>
<div id="group_account">
<!-- <div class="row">
<label class="mt-2">Akun Kas</label>
<select id="account_kas" class="form-control"></select>
<div class="col-md-7">
<div id="group_account">
<select id="account_kas" class="form-control"></select>
</div>
</div>
<div class="col-md-5">
<div class="input-group">
<span class="input-group-text">Rp</span>
<input type="text" id="account[]" class="form-control format-rupiah">
</div>
</div>
</div>
<button class="btn btn-warning btn-sm mt-4">Tambah Account</button> -->
<hr>
<label class="font-weight-bold">
Pembayaran
</label>
<div id="accountContainer">
<div class="row account-row mb-2">
<div class="col-md-6">
<select
name="account_kas[]"
class="form-control account_kas">
</select>
</div>
<div class="col-md-4">
<div class="input-group">
<span class="input-group-text">Rp</span>
<input
type="text"
name="nominal[]"
class="form-control nominal format-rupiah">
</div>
</div>
<div class="col-md-2">
<button
type="button"
class="btn btn-sm btn-danger btn-remove-account w-100"
disabled>
Hapus
</button>
</div>
</div>
</div>
<div class="mt-2">
<button
type="button"
id="btnTambahAccount"
class="btn btn-warning btn-sm">
+ Tambah Account
</button>
</div>
<div class="mt-3">
<div class="row">
<div class="col-md-4">
<!-- total qty * harga beli -->
<h5>
Total :
<span id="totalHppBarang">
Rp 0
</span>
</h5>
</div>
<div class="col-md-4">
<!-- total accounting -->
<h5>
Total :
<span id="totalAccount">
Rp 0
</span>
</h5>
</div>
<div class="col-md-4">
<!-- balance accounting + total hpp barang -->
<h5>
Total :
<span id="totalAll">
Rp 0
</span>
</h5>
</div>
</div>
</div>
</div>
@@ -89,59 +196,6 @@
</div>
</div>
<!-- MODAL BARANAG KELUAR -->
<div class="modal fade" id="modalKeluar">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header bg-danger text-white">
<h5>Barang Keluar</h5>
</div>
<div class="modal-body">
<div id="group_account_biaya">
<label class="mt-2">Akun Biaya</label>
<select id="account_biaya" class="form-control"></select>
</div>
<div>
<label>Tanggal</label>
<input type="date" id="tanggal_keluar" class="form-control">
</div>
<div>
<label class="mt-2">Gudang</label>
<select id="warehouse_id_keluar" class="form-control"></select>
</div>
<div class="d-none" id="group_barang_keluar">
<label class="mt-2">Barang</label>
<select id="barang_id" class="form-control select-search"></select>
</div>
<div class="d-none" id="group_qty_keluar">
<label class="mt-2">Jumlah Barang</label>
<input type="number" id="qty_keluar" class="form-control">
</div>
<div class="mb-2 d-none" id="group_keterangan">
<label for="keterangan" class="form-label">Keterangan</label>
<textarea class="form-control" id="keterangan_keluar" rows="3"></textarea>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-danger" id="btnKeluar">Keluarkan</button>
</div>
</div>
</div>
</div>
<!-- MODAL DETAIL -->
<div class="modal fade" id="modalDetail">
<div class="modal-dialog modal-lg">
@@ -158,46 +212,13 @@
</div>
<div class="modal fade" id="modalAdjust">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header bg-warning">
<h5>Penyesuaian Stok</h5>
</div>
<div class="modal-body">
<input type="hidden" id="adjust_item_id">
<label>Qty Adjustment</label>
<input type="number" id="adjust_qty" class="form-control">
<small>Gunakan minus (-) jika mengurangi</small>
<label class="mt-2">Gudang</label>
<select id="adjust_warehouse" class="form-control"></select>
<label class="mt-2">Keterangan</label>
<input type="text" id="adjust_ket" class="form-control">
</div>
<div class="modal-footer">
<button class="btn btn-warning" id="btnAdjust">Simpan</button>
</div>
</div>
</div>
</div>
<script>
$(function(){
let action = 'add';
let selectedItemId = null;
let accountOption = '';
// ================= DATATABLE =================
let table = $('#tableItems').DataTable({
@@ -210,24 +231,26 @@ let table = $('#tableItems').DataTable({
});
// ================= LOAD AKUN KAS =================
// function loadKas(){
// $.get("<?= base_url('items/get_accounts'); ?>",res=>{
// let opt='<option value="">-- PILIH KAS --</option>';
// res.forEach(a=>{
// opt += `<option value="${a.id}">${a.nama_akun}</option>`;
// });
// $('#account_kas').html(opt);
// },'json');
// }
function loadKas(){
$.get("<?= base_url('items/get_accounts'); ?>",res=>{
let opt='<option value="">-- PILIH KAS --</option>';
res.forEach(a=>{
opt += `<option value="${a.id}">${a.nama_akun}</option>`;
$.get("<?= base_url('items/get_accounts'); ?>",function(res){
accountOption = '<option value="">-- PILIH KAS --</option>';
res.forEach(function(a){
accountOption += `
<option value="${a.id}">
${a.nama_akun}
</option>
`;
});
$('#account_kas').html(opt);
},'json');
}
// ================= LOAD AKUN KAS =================
function loadKasOut(){
$.get("<?= base_url('items/get_accounts_biaya'); ?>",res=>{
let opt='<option value="">-- PILIH KAS --</option>';
res.forEach(a=>{
opt += `<option value="${a.id}">${a.nama_akun}</option>`;
});
$('#account_biaya').html(opt);
$('.account_kas').html(accountOption);
},'json');
}
@@ -241,27 +264,105 @@ function loadGudang(){
$('#warehouse_id, #adjust_warehouse').html(opt);
},'json');
}
// ================= LOAD GUDANG =================
function loadGudangList(){
$.get("<?= base_url('warehouses/list'); ?>",res=>{
let opt='<option value="">-- PILIH GUDANG --</option>';
res.forEach(w=>{
opt += `<option value="${w.id}">${w.nama}</option>`;
function loadKode() {
$.get("<?= base_url('kodebarang/list'); ?>", function(res) {
let opt = '<option value="">-- PILIH KODE --</option>';
res.forEach(function(a){
opt += `
<option value="${a.id}"
data-type="${a.tracking_type}">
${a.kode_barang} - ${a.nama}
</option>
`;
});
$('#warehouse_id_keluar').html(opt);
},'json');
$('#kode_barang').html(opt).trigger('change');
}, 'json');
}
function loadKode(){
$.get("<?= base_url('kodebarang/list'); ?>",res=>{
let opt='<option value="">-- PILIH KODE --</option>';
res.forEach(a=>{
opt += `<option value="${a.id}">${a.kode_barang} - ${a.nama}</option>`;
});
$('#kode_barang').html(opt);
},'json');
function addAccountRow(){
let html = `
<div class="row account-row mb-2">
<div class="col-md-6">
<select
name="account_kas[]"
class="form-control account_kas">
${accountOption}
</select>
</div>
<div class="col-md-4">
<div class="input-group">
<span class="input-group-text">
Rp
</span>
<input
type="text"
name="nominal[]"
class="form-control nominal format-rupiah">
</div>
</div>
<div class="col-md-2">
<button
type="button"
class="btn btn-sm btn-danger btn-remove-account w-100">
Hapus
</button>
</div>
</div>
`;
$('#accountContainer').append(html);
hitungTotal();
}
$(document).on('change', '#kode_barang', function () {
let type = $(this).find(':selected').data('type');
if(type == 'QTY'){
$('#group_tracking_qty').show();
}else{
$('#group_tracking_qty').hide();
$('#tracking_qty').val('');
}
});
$('#btnTambahAccount').click(function(){
addAccountRow();
});
$(document).on('click','.btn-remove-account',function(){
$(this).closest('.account-row').remove();
hitungTotal();
});
// function hitungTotal(){
// let total = 0;
// $('.nominal').each(function(){
// let angka = $(this)
// .val()
// .replace(/\./g,'')
// .replace(/,/g,'');
// total += Number(angka || 0);
// });
// $('#totalAccount').text(formatRupiah(total.toString()));
// }
$(document).on('keyup','.nominal',function(){
hitungTotal();
});
$(document).on('keyup','#harga_beli',function(){
hitungTotal();
});
$(document).on('keyup change','#qty',function(){
hitungTotal();
});
// ================= RESET FORM =================
function resetForm(){
$('#id').val('');
@@ -270,6 +371,44 @@ function resetForm(){
$('#harga_beli').val('');
$('#harga_jual').val('');
$('#tanggal_beli').val('');
$('#tracking_qty').val('');
$('#group_tracking_qty').hide();
$('#accountContainer').html(`
<div class="row account-row mb-2">
<div class="col-md-6">
<select
name="account_kas[]"
class="form-control account_kas">
${accountOption}
</select>
</div>
<div class="col-md-4">
<div class="input-group">
<span class="input-group-text">Rp</span>
<input
type="text"
name="nominal[]"
class="form-control nominal format-rupiah">
</div>
</div>
<div class="col-md-2">
<button
class="btn btn-sm btn-danger btn-remove-account w-100"
disabled>
Hapus
</button>
</div>
</div>
`);
$('#totalAccount').text('Rp 0');
$('#totalHppBarang').text('Rp 0');
$('#totalAll')
.text('Balance')
.removeClass('text-danger')
.addClass('text-success');
$('#btnSimpan').prop('disabled',true);
}
// ================= INIT LOAD =================
@@ -324,6 +463,40 @@ $('#btnSimpan').click(function(){
let btn = $(this);
let qtys = parseFloat($('#qty').val()) || 0;
let totalHpp = qtys * rupiahToNumber($('#harga_beli').val());
let totalAccount = 0;
$('.nominal').each(function(){
totalAccount += rupiahToNumber($(this).val());
});
if(totalHpp != totalAccount){
Swal.fire({
icon:'warning',
title:'Belum Balance',
text:'Total pembayaran harus sama dengan Total Harga Beli.'
});
return;
}
let account = [];
$('.account-row').each(function(){
account.push({
account_id: $(this).find('.account_kas').val(),
nominal: $(this)
.find('.nominal')
.val()
});
});
// data.accounts = account;
let data = {
id: $('#id').val(),
kode_id: $('#kode_barang').val(),
@@ -332,8 +505,10 @@ $('#btnSimpan').click(function(){
harga_beli: $('#harga_beli').val(),
harga_jual: $('#harga_jual').val(),
warehouse_id: $('#warehouse_id').val(),
account_kas: $('#account_kas').val(),
// account_kas: $('#account_kas').val(),
account_kas: account,
tanggal_beli: $('#tanggal_beli').val(),
tracking_qty: $('#tracking_qty').val(),
status: `draft`
};
@@ -384,122 +559,74 @@ $('#btnSimpan').click(function(){
});
// =====================================================
// ================= START URUSAN KELUAR ===============
// =====================================================
// ================= RESET FORM KELUAR =================
function resetFormKeluar(){
$('#tanggal_keluar').val('');
$('#qty_keluar').val('');
$('#keterangan_keluar').val('');
}
// ================= PRINT BARCODE =================
$(document).on('click', '.btn-print', function () {
loadKasOut();
loadGudangList();
// ================= BARANG KELUAR ================= account_biaya warehouse_id barang_id
$('.btn-out').click(function(){
// action='add';
resetFormKeluar();
let id = $(this).data('id');
$('#warehouse_id').closest('div').show();
$('#account_biaya').closest('div').show();
$('#modalKeluar').modal('show');
loadGudangList();
loadKasOut();
});
Swal.fire({
title: 'Cetak Barcode',
html: `
<div class="text-left">
$('#warehouse_id_keluar').on('change', function(){
let warehouse_id = $(this).val();
// $('#wrap_item_select').addClass('d-none');
$('#barang_id').html('<option value="">Loading...</option>');
loadItems(warehouse_id);
});
<div class="form-group">
<label for="print_column" class="font-weight-bold">
Jumlah Kolom
</label>
<select id="print_column" class="form-control">
<option value="1">1 Kolom</option>
<option value="2">2 Kolom</option>
<option value="3" selected>3 Kolom (Rekomendasi)</option>
</select>
</div>
// ================= ITEMS =================
function loadItems(warehouse_id){
return $.get("<?= base_url('items/get_items_by_wh_id/'); ?>" + warehouse_id, function(res){
if(res && res.length){
let opt = '<option value="">-- PILIH BARANG --</option>';
res.forEach(i=>{
opt += `<option value="${i.id}">
${i.kode_detail} - ${i.nama_barang} (Stok: ${i.stok})
</option>`;
});
$('#barang_id').html(opt);
$('#group_barang_keluar').removeClass('d-none');
$('#group_qty_keluar').removeClass('d-none');
$('#group_keterangan').removeClass('d-none');
} else {
$('#barang_id').html(opt);
}
}, 'json');
}
<div class="form-group mb-0">
<label for="duplicat" class="font-weight-bold">
Jumlah Cetak per Barcode
</label>
<select id="duplicat" class="form-control">
<option value="1" selected>1 Label</option>
<option value="2">2 Label</option>
</select>
</div>
// ================= SAVE =================
$('#btnKeluar').click(function(){
</div>
`,
icon: 'question',
showCancelButton: true,
confirmButtonText: 'Cetak',
cancelButtonText: 'Batal',
confirmButtonColor: '#f0ad4e',
preConfirm: () => {
let btn = $(this);
let column = $('#print_column').val();
let duplicat = $('#duplicat').val();
let data = {
account_biaya: $('#account_biaya').val(),
tanggal_keluar: $('#tanggal_keluar').val(),
warehouse_id_keluar: $('#warehouse_id_keluar').val(),
barang_id: $('#barang_id').val(),
qty_keluar: $('#qty_keluar').val(),
keterangan_keluar: $('#keterangan_keluar').val()
};
return {
column: column,
duplicat: duplicat
};
let url = "<?= base_url('items/keluarkan'); ?>";
// 🔥 loading state
btn.prop('disabled', true).html('Menyimpan...');
$.post(url, data, function(res){
if(res.status){
$('#modalItem').modal('hide');
table.ajax.reload(null,false);
Swal.fire({
icon: 'success',
title: 'Berhasil',
text: res.message || 'Data berhasil disimpan',
timer: 1500,
showConfirmButton: false
});
}else{
Swal.fire({
icon: 'error',
title: 'Gagal',
text: res.message || 'Terjadi kesalahan'
});
}
},'json')
.fail(function(){
}).then((result) => {
Swal.fire({
icon: 'error',
title: 'Error',
text: 'Koneksi ke server gagal'
});
if (!result.isConfirmed) {
return;
}
window.open(
"<?= base_url('items/print_barcode/'); ?>" +
id +
"?column=" + result.value.column +
"&duplicat=" + result.value.duplicat,
"_blank"
);
})
.always(function(){
btn.prop('disabled', false).html('Keluarkan');
});
});
// =====================================================
// ================= END URUSAN KELUAR ===============
// =====================================================
// ================= DELETE =================
$(document).on('click','.btn-delete',function(){
@@ -680,78 +807,75 @@ $(document).on('click','.btn-detail',function(){
});
// ================= OPEN ADJUST =================
$(document).on('click','.btn-adjust',function(){
function rupiahToNumber(value){
let id = $(this).data('id');
if(!value) return 0;
if(!id){
alert('Pilih item dari tabel (gunakan tombol Adjust di baris)');
return;
}
let s = value.toString().trim();
s = s.replace(/\.(?=\d{3}(\.|$))/g, '');
s = s.replace(/,/g, '');
return parseFloat(s) || 0;
selectedItemId = id;
$('#adjust_item_id').val(id);
$('#adjust_qty').val('');
$('#adjust_ket').val('');
}
$('#modalAdjust').modal('show');
});
function hitungTotal(){
// ================= SAVE ADJUST =================
$('#btnAdjust').click(function(){
// ==========================
// HPP BARANG
// ==========================
let btn = $(this);
let qty = parseFloat($('#qty').val()) || 0;
let hargaBeli = rupiahToNumber($('#harga_beli').val());
let data = {
item_id: $('#adjust_item_id').val(),
qty: $('#adjust_qty').val(),
warehouse_id: $('#adjust_warehouse').val(),
keterangan: $('#adjust_ket').val()
};
let totalHpp = qty * hargaBeli;
// 🔥 loading state
btn.prop('disabled', true).html('Menyimpan...');
$.post("<?= base_url('items/adjust'); ?>", data, function(res){
// ==========================
// ACCOUNTING
// ==========================
if(res.status){
let totalAccount = 0;
$('#modalAdjust').modal('hide');
table.ajax.reload(null,false);
$('.nominal').each(function(){
Swal.fire({
icon: 'success',
title: 'Berhasil',
text: res.message || 'Stok berhasil disesuaikan',
timer: 1500,
showConfirmButton: false
});
totalAccount += rupiahToNumber($(this).val());
}else{
Swal.fire({
icon: 'error',
title: 'Gagal',
text: res.message || 'Gagal melakukan penyesuaian'
});
}
},'json')
.fail(function(){
Swal.fire({
icon: 'error',
title: 'Error',
text: 'Koneksi ke server gagal'
});
})
.always(function(){
btn.prop('disabled', false).html('Simpan');
});
});
// ==========================
// BALANCE
// ==========================
let balance = totalAccount - totalHpp;
$('#totalHppBarang').text(formatRupiah(totalHpp.toString()));
$('#totalAccount').text(formatRupiah(totalAccount.toString()));
if(balance == 0){
$('#totalAll')
.removeClass('text-danger')
.addClass('text-success')
.text('Balance');
$('#btnSimpan').prop('disabled',false);
}else{
let prefix = balance > 0 ? '+' : '-';
$('#totalAll')
.removeClass('text-success')
.addClass('text-danger')
.text(prefix + ' ' + formatRupiah(Math.abs(balance).toString()));
$('#btnSimpan').prop('disabled',true);
}
}
});
</script>
+406
View File
@@ -0,0 +1,406 @@
<style>
body {
background: #f5f7fb;
}
/* ===========================
CARD
=========================== */
.main-card,
.scanner-card,
.table-card {
border: none;
border-radius: 18px;
overflow: hidden;
/*box-shadow: 0 8px 25px rgba(0, 0, 0, .08);*/
}
/* ===========================
HEADER
=========================== */
.header-gradient {
background: linear-gradient(135deg, #f59e0b, #d97706);
color: #fff;
padding: 22px 28px;
}
.info-box {
background: rgba(255,255,255,.18);
border-radius: 12px;
padding: 12px 16px;
}
.progress {
height: 8px;
border-radius: 20px;
}
/* ===========================
SCANNER
=========================== */
.barcode-avatar {
width: 110px;
height: 110px;
margin: auto;
border-radius: 50%;
background: #fff7e6;
color: #f59e0b;
display: flex;
justify-content: center;
align-items: center;
font-size: 45px;
}
.barcode-code {
font-size: 22px;
font-weight: 700;
letter-spacing: 2px;
}
.form-control-lg {
height: 60px;
font-size: 22px;
border-radius: 12px;
}
.form-control:focus {
border-color: #f59e0b;
box-shadow: 0 0 0 .2rem rgba(245, 158, 11, .2);
}
.scan-note {
color: #6c757d;
font-size: 13px;
}
.camera-btn {
height: 55px;
border-radius: 12px;
font-size: 17px;
}
/* ===========================
TABLE
=========================== */
.table thead th {
background: #d97706;
color: #fff;
border: none;
vertical-align: middle;
}
.table tbody td {
vertical-align: middle;
}
.table tbody tr:hover {
background: #fffaf0;
}
/* ===========================
BADGE
=========================== */
.badge-success-soft {
display: inline-block;
padding: 6px 14px;
border-radius: 30px;
background: #d1fae5;
color: #065f46;
font-weight: 600;
}
.badge-danger-soft {
display: inline-block;
padding: 6px 14px;
border-radius: 30px;
background: #fee2e2;
color: #991b1b;
font-weight: 600;
}
.badge-warning-soft {
display: inline-block;
padding: 6px 14px;
border-radius: 30px;
background: #fef3c7;
color: #92400e;
font-weight: 600;
}
#reader{
width:100%;
min-height:280px;
border-radius:12px;
overflow:hidden;
}
</style>
<div class="container mt-4">
<div class="card main-card">
<!-- HEADER -->
<div class="header-gradient">
<div class="row align-items-center">
<div class="col-md-8">
<h3 class="mb-1">
<i class="fa fa-barcode"></i>
Riwayat Perpindahan Barang
</h3>
<div class="small">
Silahkan scan / input QR id Barang
</div>
</div>
<div class="col-md-4 text-right">
</div>
</div>
</div>
<!-- BODY -->
<div class="card-body">
<div class="row">
<!-- LEFT -->
<div class="col-lg-4 mb-4">
<div class="card scanner-card">
<div class="card-body">
<div class="barcode-avatar">
<i class="bi bi-qr-code-scan"></i>
</div>
<div class="text-center mt-3">
<div class="scan-note mt-2">
Scan QR ID
</div>
</div>
<hr>
<center>
<div id="scannerArea">
<input
type="text"
id="serial_number"
class="form-control form-control-lg text-center"
placeholder="Scan / Input QR ID"
autocomplete="off"
autofocus>
<small class="scan-note">
Scanner akan otomatis memanggil riwayat.
</small>
<button
type="button"
class="btn btn-warning btn-block camera-btn mt-4"
id="btnCamera">
<i class="fa fa-camera mr-2"></i>
Scan dengan Kamera
</button>
</div>
<div id="cameraArea" style="display:none;">
<div id="reader"></div>
<small class="scan-note d-block mt-2">
Arahkan QR Code ke kamera.
</small>
<button
type="button"
class="btn btn-secondary btn-block mt-3"
id="btnCloseCamera">
Tutup Kamera
</button>
</div>
</center>
<input type="hidden" id="barcode_id">
</div>
</div>
</div>
<!-- RIGHT -->
<div class="col-lg-8">
<div class="card table-card">
<div class="card-header bg-white">
<div class="d-flex justify-content-between align-items-center">
<h5 class="mb-0">
Riwayat Perpindahan Barang
</h5>
</div>
</div>
<div class="card-body border-bottom" id="itemInfo">
Belum ada data
</div>
<div class="table-responsive">
<table class="table table-hover mb-0" id="tableBarcode">
<thead>
<tr>
<th width="60">No</th>
<th>Date</th>
<th>Type</th>
<th>From</th>
<th>To</th>
<th>NOtes</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
$("#serial_number").keypress(function(e){
if(e.which==13){
searchHistory();
}
});
function searchHistory(){
let barcode=$("#serial_number").val().trim();
if(barcode=="") return;
$.ajax({
url: `<?= base_url("items/scan_history"); ?>`,
type:"POST",
dataType:"json",
data:{
barcode:barcode
},
beforeSend:function(){
$("#serial_number").prop("disabled",true);
},
success:function(r){
$("#serial_number")
.prop("disabled",false)
.val("")
.focus();
if(!r.status){
Swal.fire({
icon:"error",
text:r.message
});
return;
}
renderItem(r.item);
renderHistory(r.history);
}
});
}
function renderItem(item){
let html='';
html+='<div class="row">';
html+=' <div class="col-md-6">';
html+=' <h4 class="mb-3"><b>'+item.nama_barang+'</b></h4>';
html+=' <div class="row mb-2">';
html+=' <div class="col-4 font-weight-bold">Kode</div>';
html+=' <div class="col-1">:</div>';
html+=' <div class="col-7">'+item.kode_detail+'</div>';
html+=' </div>';
html+=' <div class="row mb-2">';
html+=' <div class="col-4 font-weight-bold">Barcode</div>';
html+=' <div class="col-1">:</div>';
html+=' <div class="col-7">'+item.barcode+'</div>';
html+=' </div>';
html+=' </div>';
html+=' <div class="col-md-6">';
html+=' <div class="row mb-2">';
html+=' <div class="col-4 font-weight-bold">Serial Number</div>';
html+=' <div class="col-1">:</div>';
html+=' <div class="col-7">'+(item.serial_number ? item.serial_number : '-')+'</div>';
html+=' </div>';
html+=' <div class="row mb-2">';
html+=' <div class="col-4 font-weight-bold">Status</div>';
html+=' <div class="col-1">:</div>';
html+=' <div class="col-7">'+item.status+'</div>';
html+=' </div>';
html+=' <div class="row mb-2">';
html+=' <div class="col-4 font-weight-bold">Qty</div>';
html+=' <div class="col-1">:</div>';
html+=' <div class="col-7">'+item.qty_sisa+' / '+item.qty_awal+'</div>';
html+=' </div>';
html+=' </div>';
html+='</div>';
$("#itemInfo").html(html);
}
function renderHistory(list){
let html='';
$.each(list,function(i,v){
html+="<tr>";
html+="<td>"+(i+1)+"</td>";
html+="<td>"+v.created_at+"</td>";
html+="<td>"+v.movement_type+"</td>";
html+="<td>"+(v.from_warehouse??v.from_type)+"</td>";
html+="<td>"+(v.to_warehouse??v.to_type)+"</td>";
html+="<td>"+v.notes+"</td>";
html+="</tr>";
});
$("#tableBarcode tbody").html(html);
}
let html5QrCode=null;
$("#btnCamera").click(function(){
$("#scannerArea").hide();
$("#cameraArea").show();
html5QrCode=new Html5Qrcode("reader");
html5QrCode.start(
{
facingMode:"environment"
},
{
fps:10,
qrbox:220
},
function(decodedText){
html5QrCode.stop().then(function(){
$("#cameraArea").hide();
$("#scannerArea").show();
$("#serial_number")
.val(decodedText);
saveSerial();
});
}
);
});
$("#btnCloseCamera").click(function(){
if(html5QrCode){
html5QrCode.stop().then(function(){
$("#cameraArea").hide();
$("#scannerArea").show();
$("#serial_number").focus();
});
}
});
</script>
+497 -75
View File
@@ -6,7 +6,7 @@
<h5>Data Barang</h5>
<div class="text-end">
<button class="btn btn-add bg-danger btn-out mr-2">Pengeluaran</button>
<button class="btn btn-warning btn-add btn-add-item mr-2">Tambah Barang</button>
<!-- <button class="btn btn-warning btn-add btn-add-item mr-2">Tambah Barang</button> -->
</div>
</div>
@@ -92,7 +92,7 @@
<!-- MODAL BARANAG KELUAR -->
<div class="modal fade" id="modalKeluar">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content">
<div class="modal-header bg-danger text-white">
@@ -101,6 +101,57 @@
<div class="modal-body">
<center>
<div id="scannerArea">
<input
type="text"
id="serial_number"
class="form-control form-control-lg text-center"
placeholder="Scan Barcode / Serial Number / Nama Barang"
autocomplete="off"
autofocus>
<small class="scan-note">
Scan atau ketik Barcode / Serial Number / Nama Barang, lalu Enter.
</small><br>
<button
type="button"
class="btn btn-warning btn-block camera-btn mt-4"
id="btnCamera">
<i class="fa fa-camera mr-2"></i>
Scan dengan Kamera
</button>
</div>
<div id="cameraArea" style="display:none;">
<div id="reader"></div>
<small class="scan-note d-block mt-2">
Arahkan QR Code ke kamera.
</small>
<button
type="button"
class="btn btn-secondary btn-block mt-3"
id="btnCloseCamera">
Tutup Kamera
</button>
</div>
</center>
<div style="max-height: 200px; overflow: auto;">
<div class="mt-2" id="tableBarcode"></div>
</div>
<hr>
<div id="group_account_biaya">
<label class="mt-2">Akun Biaya</label>
<select id="account_biaya" class="form-control"></select>
@@ -111,7 +162,7 @@
<input type="date" id="tanggal_keluar" class="form-control">
</div>
<div>
<!-- <div>
<label class="mt-2">Gudang</label>
<select id="warehouse_id_keluar" class="form-control"></select>
</div>
@@ -124,9 +175,8 @@
<div class="d-none" id="group_qty_keluar">
<label class="mt-2">Jumlah Barang</label>
<input type="number" id="qty_keluar" class="form-control">
</div>
<div class="mb-2 d-none" id="group_keterangan">
</div> -->
<div class="mb-2" id="group_keterangan">
<label for="keterangan" class="form-label">Keterangan</label>
<textarea class="form-control" id="keterangan_keluar" rows="3"></textarea>
</div>
@@ -195,6 +245,290 @@
<script>
// URUSAN BARCODE INPUT DAN CAMSCANNER
let daftarBarang = [];
let isSaving = false;
let searchTimer = null;
$(document).ready(function () {
$("#serial_number").focus();
$("#serial_number").on("keypress", function(e){
if(e.which==13){
e.preventDefault();
if(searchTimer){
clearTimeout(searchTimer);
}
if(!isSaving){
getBarcodeId(true);
}
}
});
$("#serial_number").on("input", function(){
let keyword = ($(this).val() || "").trim();
if(searchTimer){
clearTimeout(searchTimer);
}
if(keyword === ""){
daftarBarang = [];
renderTable();
return;
}
searchTimer = setTimeout(function(){
if(!isSaving){
getBarcodeId(false);
}
}, 300);
});
});
function getBarcodeId(isScanner = false){
let barcode = ($("#serial_number").val() || "").trim();
if(barcode==""){
return;
}
$.post(
"<?=base_url('items/get_barcode_item');?>",
{ barcode: barcode },
function(r){
if(!r.status){
Swal.fire({
icon:"error",
title:"Oops...",
text:r.message
});
$("#serial_number").val("").focus();
return;
}
let rows = Array.isArray(r.data) ? r.data : [];
if(rows.length === 0){
Swal.fire({
icon:"warning",
title:"Data kosong",
text:"Tidak ada barang yang cocok."
});
$("#serial_number").val("").focus();
return;
}
daftarBarang = rows;
renderTable();
if(isScanner){
$("#serial_number").val("").focus();
}
},
"json"
);
}
function renderTable() {
let html = '';
html += '<div class="table-responsive">';
html += '<table class="table table-bordered table-hover table-sm mb-0">';
html += '<thead>';
html += '<tr>';
html += '<th width="50">No</th>';
html += '<th width="180">Barcode</th>';
html += '<th width="120">Kode Barang</th>';
html += '<th>Nama Barang</th>';
html += '<th width="180">Serial Number</th>';
html += '<th width="70" class="text-center">Pilih</th>';
html += '</tr>';
html += '</thead>';
html += '<tbody>';
if (daftarBarang.length == 0) {
html += `
<tr>
<td colspan="6" class="text-center text-muted py-4">
Belum ada barang yang discan.
</td>
</tr>
`;
} else {
$.each(daftarBarang, function (i, v) {
html += `
<tr>
<td>${i + 1}</td>
<td><strong>${v.barcode}</strong></td>
<td>${v.kode_detail}</td>
<td>${v.nama_barang}</td>
<td>${v.serial_number ? v.serial_number : '-'}</td>
<td class="text-center align-middle">
<div class="form-check d-flex justify-content-center mb-0">
<input
class="form-check-input pilihBarang"
type="checkbox"
value="${v.barcode_id}"
data-barcode="${v.barcode}">
</div>
</td>
</tr>
`;
});
}
html += '</tbody>';
html += '</table>';
html += '</div>';
$('#tableBarcode').html(html);
if (daftarBarang.length === 1) {
$(".pilihBarang").prop("checked", false);
$(".pilihBarang").first().prop("checked", true);
}
}
// function renderTable(){
// let html='';
// html+='<div class="table-responsive">';
// html+='<table class="table table-bordered table-hover table-sm mb-0">';
// html+='<thead>';
// html+='<tr>';
// html+='<th width="50">No</th>';
// html+='<th width="180">Barcode</th>';
// html+='<th width="150">Kode Barang</th>';
// html+='<th>Nama Barang</th>';
// html+='<th width="180">Serial Number</th>';
// html+='<th width="80" class="text-center">Action</th>';
// html+='</tr>';
// html+='</thead>';
// html+='<tbody>';
// if(daftarBarang.length==0){
// html+='<tr>';
// html+='<td colspan="6" class="text-center text-muted py-4">';
// html+='Belum ada barcode yang discan.';
// html+='</td>';
// html+='</tr>';
// }else{
// $.each(daftarBarang,function(i,v){
// html+='<tr>';
// html+='<td>'+(i+1)+'</td>';
// html+='<td><strong>'+v.barcode+'</strong></td>';
// html+='<td>'+v.kode_detail+'</td>';
// html+='<td>'+v.nama_barang+'</td>';
// html+='<td>'+(v.serial_number?v.serial_number:'-')+'</td>';
// html+='<td class="text-center">';
// html+='<button class="btn btn-sm btn-outline-danger btnRemove" data-id="'+v.barcode_id+'">';
// html+='<i class="bi bi-trash"></i>';
// html+='</button>';
// html+='</td>';
// html+='</tr>';
// });
// }
// html+='</tbody>';
// html+='</table>';
// html+='</div>';
// $("#tableBarcode").html(html);
// }
$(document).on("change", ".pilihBarang", function(){
if($(this).is(":checked")){
$(".pilihBarang").not(this).prop("checked", false);
}
});
function getSelectedBarcodeRows(){
let selectedIds = $(".pilihBarang:checked").map(function(){
return String($(this).val());
}).get();
if(selectedIds.length === 0){
return [];
}
return daftarBarang.filter(function(v){
return selectedIds.indexOf(String(v.barcode_id)) !== -1;
});
}
let html5QrCode=null;
$("#btnCamera").click(function(){
$("#scannerArea").hide();
$("#cameraArea").show();
html5QrCode=new Html5Qrcode("reader");
html5QrCode.start(
{
facingMode:"environment"
},
{
fps:10,
qrbox:220
},
function(decodedText){
html5QrCode.stop().then(function(){
$("#cameraArea").hide();
$("#scannerArea").show();
$("#serial_number")
.val(decodedText);
getBarcodeId();
});
}
);
});
$("#btnCloseCamera").click(function(){
if(html5QrCode){
html5QrCode.stop().then(function(){
$("#cameraArea").hide();
$("#scannerArea").show();
$("#serial_number").focus();
});
}
});
// URUSAN BARCODE INPUT DAN CAMSCANNER
$(function(){
let action = 'add';
@@ -278,20 +612,20 @@ loadGudang();
loadKas();
loadKode();
// ================= ADD =================
$('.btn-add-item').click(function(){
action='add';
resetForm();
// // ================= ADD =================
// $('.btn-add-item').click(function(){
// action='add';
// resetForm();
$('#qty').closest('div').show();
$('#kode_barang').closest('div').show();
$('#warehouse_id').closest('div').show();
$('#account_kas').closest('div').show();
$('#modalItem').modal('show');
loadGudang();
loadKas();
loadKode();
});
// $('#qty').closest('div').show();
// $('#kode_barang').closest('div').show();
// $('#warehouse_id').closest('div').show();
// $('#account_kas').closest('div').show();
// $('#modalItem').modal('show');
// loadGudang();
// loadKas();
// loadKode();
// });
// ================= EDIT =================
$(document).on('click','.btn-editItem',function(){
@@ -392,8 +726,9 @@ $('#btnSimpan').click(function(){
// ================= RESET FORM KELUAR =================
function resetFormKeluar(){
$('#tanggal_keluar').val('');
$('#qty_keluar').val('');
$('#keterangan_keluar').val('');
daftarBarang = [];
renderTable();
}
loadKasOut();
@@ -411,6 +746,12 @@ $('.btn-out').click(function(){
loadKasOut();
});
$('#modalKeluar').on('shown.bs.modal', function () {
setTimeout(function () {
$('#serial_number').trigger('focus').select();
}, 100);
});
$('#warehouse_id_keluar').on('change', function(){
let warehouse_id = $(this).val();
@@ -444,28 +785,41 @@ function loadItems(warehouse_id){
$('#btnKeluar').click(function(){
let btn = $(this);
let selectedRows = getSelectedBarcodeRows();
if(selectedRows.length !== 1){
Swal.fire({
icon: 'warning',
title: 'Pilih 1 barang',
text: 'Pengeluaran hanya boleh 1 barcode item per proses.'
});
return;
}
let selected = selectedRows[0];
let data = {
account_biaya: $('#account_biaya').val(),
tanggal_keluar: $('#tanggal_keluar').val(),
warehouse_id_keluar: $('#warehouse_id_keluar').val(),
barang_id: $('#barang_id').val(),
qty_keluar: $('#qty_keluar').val(),
keterangan_keluar: $('#keterangan_keluar').val()
account_biaya: $('#account_biaya').val(),
tanggal_keluar: $('#tanggal_keluar').val(),
keterangan_keluar: $('#keterangan_keluar').val(),
selected_barcode: selected.barcode_id
};
let url = "<?= base_url('items/keluarkan'); ?>";
// 🔥 loading state
btn.prop('disabled', true).html('Menyimpan...');
$.post(url, data, function(res){
if(res.status){
$('#modalItem').modal('hide');
$('#modalKeluar').modal('hide');
table.ajax.reload(null,false);
daftarBarang = [];
renderTable();
$("#serial_number").val("");
Swal.fire({
icon: 'success',
title: 'Berhasil',
@@ -502,68 +856,68 @@ $('#btnKeluar').click(function(){
// ================= END URUSAN KELUAR ===============
// =====================================================
// ================= DELETE =================
$(document).on('click','.btn-delete',function(){
// // ================= DELETE =================
// $(document).on('click','.btn-delete',function(){
let id = $(this).data('id');
let btn = $(this);
// let id = $(this).data('id');
// let btn = $(this);
Swal.fire({
title: 'Hapus data?',
text: "Data tidak bisa dikembalikan!",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'Ya, hapus!',
cancelButtonText: 'Batal'
}).then((result) => {
// Swal.fire({
// title: 'Hapus data?',
// text: "Data tidak bisa dikembalikan!",
// icon: 'warning',
// showCancelButton: true,
// confirmButtonColor: '#d33',
// cancelButtonColor: '#3085d6',
// confirmButtonText: 'Ya, hapus!',
// cancelButtonText: 'Batal'
// }).then((result) => {
if(result.isConfirmed){
// if(result.isConfirmed){
btn.prop('disabled', true).html('Menghapus...');
// btn.prop('disabled', true).html('Menghapus...');
$.get("<?= base_url('items/delete/'); ?>"+id,function(res){
// $.get("<?= base_url('items/delete/'); ?>"+id,function(res){
if(res.status){
// if(res.status){
table.ajax.reload(null,false);
// table.ajax.reload(null,false);
Swal.fire({
icon: 'success',
title: 'Berhasil',
text: res.message || 'Data berhasil dihapus',
timer: 1500,
showConfirmButton: false
});
// Swal.fire({
// icon: 'success',
// title: 'Berhasil',
// text: res.message || 'Data berhasil dihapus',
// timer: 1500,
// showConfirmButton: false
// });
}else{
// }else{
Swal.fire({
icon: 'error',
title: 'Gagal',
text: res.message || 'Gagal menghapus data'
});
}
// Swal.fire({
// icon: 'error',
// title: 'Gagal',
// text: res.message || 'Gagal menghapus data'
// });
// }
},'json')
.fail(function(){
// },'json')
// .fail(function(){
Swal.fire({
icon: 'error',
title: 'Error',
text: 'Koneksi ke server gagal'
});
// Swal.fire({
// icon: 'error',
// title: 'Error',
// text: 'Koneksi ke server gagal'
// });
})
.always(function(){
btn.prop('disabled', false).html('Hapus');
});
// })
// .always(function(){
// btn.prop('disabled', false).html('Hapus');
// });
}
});
// }
// });
});
// });
// ================= DETAIL =================
$(document).on('click','.btn-detail',function(){
@@ -681,5 +1035,73 @@ $('#btnAdjust').click(function(){
});
// ================= PRINT BARCODE =================
$(document).on('click', '.btn-print', function () {
let id = $(this).data('id');
Swal.fire({
title: 'Cetak Barcode',
html: `
<div class="text-left">
<div class="form-group">
<label for="print_column" class="font-weight-bold">
Jumlah Kolom
</label>
<select id="print_column" class="form-control">
<option value="1">1 Kolom</option>
<option value="2">2 Kolom</option>
<option value="3" selected>3 Kolom (Rekomendasi)</option>
</select>
</div>
<div class="form-group mb-0">
<label for="duplicat" class="font-weight-bold">
Jumlah Cetak per Barcode
</label>
<select id="duplicat" class="form-control">
<option value="1" selected>1 Label</option>
<option value="2">2 Label</option>
</select>
</div>
</div>
`,
icon: 'question',
showCancelButton: true,
confirmButtonText: 'Cetak',
cancelButtonText: 'Batal',
confirmButtonColor: '#f0ad4e',
preConfirm: () => {
let column = $('#print_column').val();
let duplicat = $('#duplicat').val();
return {
column: column,
duplicat: duplicat
};
}
}).then((result) => {
if (!result.isConfirmed) {
return;
}
window.open(
"<?= base_url('items/print_barcode/'); ?>" +
id +
"?column=" + result.value.column +
"&duplicat=" + result.value.duplicat,
"_blank"
);
});
});
});
</script>
+40 -4
View File
@@ -16,6 +16,9 @@
<th>Kode</th>
<th>Nama</th>
<th>Limit Stok</th>
<th>Tracking Type</th>
<th>Need Serial</th>
<th>Unit</th>
<th>Aksi</th>
</tr>
</thead>
@@ -49,6 +52,25 @@
<label>Limit Stok</label>
<input type="number" id="limit_stock" class="form-control">
<label class="mt-2">Tracking Type</label>
<select id="tracking_type" class="form-control">
<option value="UNIT">UNIT</option>
<option value="QTY">QTY</option>
</select>
<label class="mt-2">Need Serial Number</label>
<select id="need_serial_number" class="form-control">
<option value="false">Tidak</option>
<option value="true">Ya</option>
</select>
<label class="mt-2">Unit</label>
<select id="unit" class="form-control">
<option value="PCS">PCS</option>
<option value="Meter">Meter</option>
<option value="Lembar">Lembar</option>
</select>
</div>
<div class="modal-footer">
@@ -77,6 +99,9 @@ $(function(){
$('#kode').val('');
$('#nama').val('');
$('#limit_stock').val('');
$('#tracking_type').val('UNIT');
$('#need_serial_number').val('false');
$('#unit').val('PCS');
}
// ADD
@@ -96,6 +121,9 @@ $(function(){
$('#kode').val(res.kode_barang);
$('#nama').val(res.nama);
$('#limit_stock').val(res.limit_stock);
$('#tracking_type').val((res.tracking_type || 'UNIT').toUpperCase());
$('#need_serial_number').val((res.need_serial_number || 'false').toLowerCase());
$('#unit').val(res.unit || 'PCS');
action = 'edit';
$('#modalKodebarang').modal('show');
@@ -110,11 +138,19 @@ $(function(){
id: $('#id').val(),
kode: $('#kode').val(),
nama: $('#nama').val(),
limit_stock: $('#limit_stock').val()
limit_stock: $('#limit_stock').val(),
tracking_type: $('#tracking_type').val(),
need_serial_number: $('#need_serial_number').val(),
unit: $('#unit').val()
};
if(!data.kode || !data.nama || !data.limit_stock){
Swal.fire('Warning','Nama gudang wajib diisi','warning');
if(!data.kode || !data.nama){
Swal.fire('Warning','Kode dan nama barang wajib diisi','warning');
return;
}
if(data.limit_stock === '' || parseInt(data.limit_stock) < 0){
Swal.fire('Warning','Limit stok wajib diisi dan tidak boleh negatif','warning');
return;
}
@@ -141,7 +177,7 @@ $(function(){
let id = $(this).data('id');
Swal.fire({
title:'Hapus gudang?',
title:'Hapus kode barang?',
text:'Pastikan tidak dipakai item',
icon:'warning',
showCancelButton:true
+41 -1
View File
@@ -16,10 +16,13 @@
<input type="date" id="tanggal_sampai" class="form-control">
</div>
<div class="col-md-4 d-flex align-items-end">
<div class="col-md-4 d-flex align-items-end gap-2">
<button type="submit" class="btn btn-warning w-100">
Tampilkan
</button>
<button type="button" class="btn btn-danger w-100" id="generate_pdf">
Generate PDF
</button>
</div>
</form>
@@ -168,5 +171,42 @@ $(function(){
loadData(dari, sampai);
});
$('#generate_pdf').on('click', function () {
const tanggal_dari = $('#tanggal_dari').val() || '0000-00-00';
const tanggal_sampai = $('#tanggal_sampai').val() || '0000-00-00';
let btn = $(this);
btn.prop('disabled', true).html('Generate PDF...');
$.ajax({
url: "<?= base_url('generatelabarugi/generatepdf'); ?>"
+ '/' + tanggal_dari
+ '/' + tanggal_sampai,
type: "GET",
xhrFields: {
responseType: 'blob' // penting untuk PDF
},
success: function (response) {
let blob = new Blob([response], { type: 'application/pdf' });
let url = window.URL.createObjectURL(blob);
// buka di tab baru
window.open(url, '_blank');
},
error: function () {
Swal.fire('Error', 'Gagal generate PDF', 'error');
},
complete: function(){
btn.prop('disabled', false).html('Generate PDF');
}
});
});
});
</script>
+53 -3
View File
@@ -18,10 +18,12 @@
<input type="date" name="tanggal_sampai" id="tanggal_sampai" class="form-control">
</div>
<div class="col-md-4 d-flex align-items-end">
<div class="col-md-4 d-flex align-items-end gap-2">
<button type="submit" class="btn btn-warning w-100">
Tampilkan
</button>
</button><br>
<button type="button" class="btn btn-danger w-100" id="generate_pdf">
<i class="fa fa-print"></i>Generate PDF</button>
</div>
</form>
@@ -76,6 +78,7 @@
</div>
<script>
$(function(){
function loadData(tanggal_dari = '', tanggal_sampai = ''){
@@ -85,6 +88,7 @@ $(function(){
tanggal_sampai: tanggal_sampai
}, function(res){
let act = '', pas = '';
// ================= ACTIVA
@@ -134,18 +138,64 @@ $(function(){
},'json');
}
// ================= LOAD AWAL
loadData();
// ================= SUBMIT FILTER
$('#filterForm').on('submit', function(e){
e.preventDefault();
let dari = $('#tanggal_dari').val();
let sampai = $('#tanggal_sampai').val();
loadData(dari, sampai);
});
// Prevent double submit
$(document).on('submit', 'form', function(e){
e.preventDefault();
});
$('#generate_pdf').on('click', function () {
const tanggal_dari = $('#tanggal_dari').val() || '0000-00-00';
const tanggal_sampai = $('#tanggal_sampai').val() || '0000-00-00';
let btn = $(this);
btn.prop('disabled', true).html('Generate PDF...');
$.ajax({
url: "<?= base_url('generateneraca/generatepdf'); ?>"
+ '/' + tanggal_dari
+ '/' + tanggal_sampai,
type: "GET",
xhrFields: {
responseType: 'blob' // penting untuk PDF
},
success: function (response) {
let blob = new Blob([response], { type: 'application/pdf' });
let url = window.URL.createObjectURL(blob);
// buka di tab baru
window.open(url, '_blank');
},
error: function () {
Swal.fire('Error', 'Gagal generate PDF', 'error');
},
complete: function(){
btn.prop('disabled', false).html('Generate PDF');
}
});
});
</script>
});
</script>
+89 -23
View File
@@ -3,20 +3,49 @@
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-3">
<h4 class="mb-0 fw-bold">Neraca Saldo</h4>
<h4 class="mb-0 fw-bold">Laporan Keuangan</h4>
</div>
<div class="row mb-3">
<div class="col-md-3">
<label class="form-label">Tanggal Awal</label>
<input type="date" id="tanggal_awal" class="form-control"
value="<?= date('Y-m-01'); ?>">
</div>
<div class="col-md-3">
<label class="form-label">Tanggal Akhir</label>
<input type="date" id="tanggal_akhir" class="form-control"
value="<?= date('Y-m-t'); ?>">
</div>
<div class="col-md-2 d-flex align-items-end gap-2">
<button class="btn btn-warning w-100" id="btnFilter">
Tampilkan
</button>
<button type="button" class="btn btn-danger" id="generate_pdf">
PDF
</button>
</div>
</div>
<div class="table-responsive">
<table class="table table-sm table-striped table-hover align-middle">
<thead class="table-light">
<tr>
<th style="width: 120px;">Kode</th>
<th width="120">Kode</th>
<th>Akun</th>
<th class="text-end">Debit</th>
<th class="text-end">Kredit</th>
</tr>
</thead>
<tbody id="tb"></tbody>
<tfoot class="table-light">
<tr class="fw-bold">
<th colspan="2" class="text-end">Total</th>
@@ -24,6 +53,7 @@
<th class="text-end text-danger" id="tk"></th>
</tr>
</tfoot>
</table>
</div>
@@ -32,35 +62,71 @@
</div>
<script>
$(function(){
$(function () {
$.get("<?= base_url('neracasaldo/get_data'); ?>", function(res){
loadData();
let html = '';
let td = 0, tk = 0;
$('#btnFilter').click(function () {
loadData();
});
res.forEach(r=>{
function loadData() {
let debit = parseFloat(r.saldo_debit) || 0;
let kredit = parseFloat(r.saldo_kredit) || 0;
$.get("<?= base_url('neracasaldo/get_data'); ?>", {
td += debit;
tk += kredit;
tanggal_awal: $('#tanggal_awal').val(),
tanggal_akhir: $('#tanggal_akhir').val()
html += `
<tr>
<td class="fw-semibold text-muted">${r.kode_akun}</td>
<td>${r.nama_akun}</td>
<td class="text-end">${debit.toLocaleString()}</td>
<td class="text-end">${kredit.toLocaleString()}</td>
</tr>`;
});
}, function (res) {
$('#tb').html(html);
$('#td').text(td.toLocaleString());
$('#tk').text(tk.toLocaleString());
let html = '';
let td = 0;
let tk = 0;
}, 'json');
res.forEach(r => {
let debit = parseFloat(r.debit) || 0;
let kredit = parseFloat(r.kredit) || 0;
td += debit;
tk += kredit;
html += `
<tr>
<td class="fw-semibold text-muted">${r.kode_akun}</td>
<td>${r.nama_akun}</td>
<td class="text-end">${debit.toLocaleString()}</td>
<td class="text-end">${kredit.toLocaleString()}</td>
</tr>
`;
});
$('#tb').html(html);
$('#td').text(td.toLocaleString());
$('#tk').text(tk.toLocaleString());
}, 'json');
}
$('#generate_pdf').click(function () {
let btn = $(this);
btn.prop('disabled', true).text('Generate PDF...');
window.open(
"<?= base_url('generateneracasaldo/generatepdf'); ?>" +
"?tanggal_awal=" + $('#tanggal_awal').val() +
"&tanggal_akhir=" + $('#tanggal_akhir').val(),
"_blank"
);
setTimeout(function () {
btn.prop('disabled', false).text('Generate PDF');
}, 1000);
});
});
</script>
+18 -4
View File
@@ -38,6 +38,8 @@
<link href="https://cdn.jsdelivr.net/npm/select2@4.0.13/dist/css/select2.min.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/select2@4.0.13/dist/js/select2.min.js"></script>
<!-- Camera Scanner -->
<script src="https://unpkg.com/html5-qrcode"></script>
</head>
<body>
@@ -82,15 +84,15 @@
<i class="bi bi-graph-up"></i> Laporan
</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item <?= ($active_menu == 'neraca_saldo') ? 'active' : '' ?>" href="<?= base_url('neracasaldo'); ?>">
<i class="bi bi-clipboard-data me-2"></i> Neraca Saldo</a>
</li>
<li><a class="dropdown-item <?= ($active_menu == 'laba_rugi') ? 'active' : '' ?>" href="<?= base_url('labarugi'); ?>">
<i class="bi bi-graph-up-arrow me-2"></i> Laba Rugi</a>
</li>
<li><a class="dropdown-item <?= ($active_menu == 'neraca') ? 'active' : '' ?>" href="<?= base_url('neraca'); ?>">
<i class="bi bi-bar-chart-line me-2"></i> Neraca</a>
</li>
<li><a class="dropdown-item <?= ($active_menu == 'neraca_saldo') ? 'active' : '' ?>" href="<?= base_url('neracasaldo'); ?>">
<i class="bi bi-clipboard-data me-2"></i> Laporan Harian</a>
</li>
</ul>
</li>
@@ -156,7 +158,7 @@
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle <?= in_array($active_menu, ['warehouses','items', 'kode_barang', 'draft_items']) ? 'active' : '' ?>" href="#" data-bs-toggle="dropdown">
<a class="nav-link dropdown-toggle <?= in_array($active_menu, ['warehouses','items', 'kode_barang', 'draft_items', 'peralatan_teknisi']) ? 'active' : '' ?>" href="#" data-bs-toggle="dropdown">
<i class="bi bi-box-seam"></i> Gudang
</a>
<ul class="dropdown-menu">
@@ -178,11 +180,23 @@
<i class="bi bi-box me-2"></i> Daftar Barang
</a>
</li><hr class="dropdown-divider">
<li>
<a class="dropdown-item <?= ($active_menu == 'item_historys') ? 'active' : '' ?>"
href="<?= base_url('items/item_barcodes_history'); ?>">
<i class="bi bi-clock me-2"></i> History Barang
</a>
</li>
<li>
<a class="dropdown-item <?= ($active_menu == 'kode_barang') ? 'active' : '' ?>"
href="<?= base_url('kodebarang'); ?>">
<i class="bi bi-code me-2"></i> Daftar Kode Barang
</a>
</li><hr class="dropdown-divider">
<li>
<a class="dropdown-item <?= ($active_menu == 'peralatan_teknisi') ? 'active' : '' ?>"
href="<?= base_url('teknisi'); ?>">
<i class="bi bi-tools me-2"></i> Peralatan Teknisi
</a>
</li>
</ul>
</li>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,155 @@
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.6/css/dataTables.bootstrap5.min.css">
<link rel="stylesheet" href="https://cdn.datatables.net/responsive/2.4.1/css/responsive.bootstrap5.min.css">
<style>
:root {
--tech-primary: #ff9800;
--tech-primary-light: #fff3e0;
--tech-primary-dark: #ff9800;
}
body {
background: #f5f7fb;
}
.tech-card {
border: none;
border-radius: 14px;
overflow: hidden;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
background: #fff;
}
.tech-card .card-header-custom {
background: linear-gradient(135deg, var(--tech-primary), var(--tech-primary-dark));
color: #fff;
padding: 14px 20px;
font-weight: 600;
font-size: 15px;
}
.tech-card .card-body {
padding: 18px;
}
.table-tech thead th {
background: #f8f9fa;
font-weight: 600;
font-size: 12px;
text-transform: uppercase;
letter-spacing: .4px;
color: #495057;
border-bottom: 2px solid #ffcc80;
padding: 10px 8px;
white-space: nowrap;
}
.table-tech tbody td {
padding: 8px;
vertical-align: middle;
font-size: 13px;
}
.table-tech td .badge {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 65px;
padding: 0.35em 0.65em;
margin: 0 auto;
}
.table-tech tbody tr:hover {
background: #fff3e0;
}
</style>
<div class="container-fluid px-3 mt-3">
<div class="d-flex align-items-center gap-2 mb-3">
<div class="bg-warning text-white p-2 rounded-3 d-flex align-items-center justify-content-center" style="width:38px;height:38px;">
<i class="bi bi-journal-text fs-5"></i>
</div>
<div class="flex-grow-1">
<h5 class="mb-0 fw-bold" style="color:#1a1a2e;">Log Barang Teknisi</h5>
<small class="text-muted">Riwayat barang yang pernah dibawa oleh teknisi</small>
</div>
<div>
<a href="<?= base_url('teknisi'); ?>" class="btn btn-secondary">
<i class="bi bi-arrow-left me-1"></i> Kembali
</a>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="card tech-card">
<div class="card-body">
<div class="table-responsive">
<table class="table table-tech table-striped w-100" id="tblLogBarang">
<thead>
<tr>
<th>No</th>
<th>Nama Teknisi</th>
<th>Kode Barang</th>
<th>Barcode</th>
<th>Nama Barang</th>
<th>Tanggal Dibawa</th>
<th>Tanggal Kembali</th>
<th>Status</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
$(function(){
$('#tblLogBarang').DataTable({
processing: true,
serverSide: false,
ajax: {
url: "<?= base_url('teknisi/get_log_data'); ?>",
dataSrc: 'data'
},
columns: [
{ data: 0 },
{ data: 1 },
{ data: 2 },
{ data: 3 },
{ data: 4 },
{ data: 5 },
{ data: 6 },
{ data: 7 }
],
pageLength: 25,
lengthMenu: [
[25, 50, 100, -1],
[25, 50, 100, 'All']
],
responsive: true,
searching: true,
paging: true,
ordering: true,
info: true,
language: {
search: "Cari:",
lengthMenu: "Tampilkan _MENU_ data",
info: "Menampilkan _START_ - _END_ dari _TOTAL_ data",
zeroRecords: "Tidak ada data",
paginate: {
first: "Awal",
last: "Akhir",
next: "&raquo;",
previous: "&laquo;"
}
}
});
});
</script>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB