diff --git a/.codegraph/.gitignore b/.codegraph/.gitignore
new file mode 100644
index 0000000..d20c0fe
--- /dev/null
+++ b/.codegraph/.gitignore
@@ -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
diff --git a/.gitignore b/.gitignore
index 6e22457..0cc1c95 100644
--- a/.gitignore
+++ b/.gitignore
@@ -35,5 +35,7 @@ user_guide_src/cilexer/pycilexer.egg-info/*
*.sublime-project
/tests/tests/
/tests/results/
+/dev-docs/
+/ai-rules/
diff --git a/application/controllers/Teknisi.php b/application/controllers/Teknisi.php
new file mode 100644
index 0000000..221b5df
--- /dev/null
+++ b/application/controllers/Teknisi.php
@@ -0,0 +1,345 @@
+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,
+ 'teknisi_id' => $teknisi ? $teknisi->user_id : null,
+ 'teknisi_name' => $teknisi ? $teknisi->full_name : null,
+ 'teknisi_code' => $teknisi ? $teknisi->employee_code : null
+ ]
+ ]);
+ }
+
+ 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 = '';
+
+ $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'
+ ? 'Dibawa'
+ : '' . htmlspecialchars($row->status, ENT_QUOTES, 'UTF-8') . '';
+
+ $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') ?: '-',
+ 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_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'),
+ 'Selesai'
+ ];
+ }
+
+ 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]);
+ }
+}
diff --git a/application/libraries/PDF_Neracasaldo.php b/application/libraries/PDF_Neracasaldo.php
index ca8c0be..19aa6ce 100644
--- a/application/libraries/PDF_Neracasaldo.php
+++ b/application/libraries/PDF_Neracasaldo.php
@@ -72,7 +72,7 @@ class PDF extends FPDF
// Judul
$this->SetFont('Arial', 'B', 13);
$this->SetTextColor(245, 140, 0);
- $this->Cell(0, 7, 'LAPORAN NERACA SALDO', 0, 1, 'C');
+ $this->Cell(0, 7, 'LAPORAN KEUANGAN', 0, 1, 'C');
$this->SetTextColor(0, 0, 0);
// Periode
diff --git a/application/models/PeralatanTeknisi_model.php b/application/models/PeralatanTeknisi_model.php
new file mode 100644
index 0000000..8db874d
--- /dev/null
+++ b/application/models/PeralatanTeknisi_model.php
@@ -0,0 +1,486 @@
+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)
+ ->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)
+ ->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 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.barcode',
+ 2 => 'i.nama_barang',
+ 3 => 'ib.serial_number',
+ 4 => 'it.status'
+ ];
+
+ $this->db
+ ->select("
+ it.barcode,
+ i.nama_barang,
+ ib.serial_number,
+ ib.qty_sisa,
+ it.status,
+ it.created_at
+ ")
+ ->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();
+ }
+}
diff --git a/application/views/laporan/neraca_saldo.php b/application/views/laporan/neraca_saldo.php
index ed0e4ec..36ced2c 100644
--- a/application/views/laporan/neraca_saldo.php
+++ b/application/views/laporan/neraca_saldo.php
@@ -5,9 +5,7 @@
Laporan Keuangan
-
+
@@ -24,10 +22,13 @@
value="= date('Y-m-t'); ?>">
-
-
diff --git a/application/views/partials/header.php b/application/views/partials/header.php
index 873326e..2abbdce 100644
--- a/application/views/partials/header.php
+++ b/application/views/partials/header.php
@@ -158,7 +158,7 @@
-
+
+
diff --git a/application/views/peralatan_teknisi/layout.php b/application/views/peralatan_teknisi/layout.php
new file mode 100644
index 0000000..0326945
--- /dev/null
+++ b/application/views/peralatan_teknisi/layout.php
@@ -0,0 +1,1145 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Peralatan Teknisi
+ Kelola barang bawaan, pengembalian, dan transfer antar teknisi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Arahkan QR ID Ke Kamera.
+
+
+ Tutup Kamera
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Scanner akan otomatis memanggil data.
+
+
+
+ Scan Dengan Kamera
+
+
+
+
+
+
+
+
+ | No |
+ Barcode |
+ Nama Barang |
+ Serial Number |
+ Teknisi |
+ Aksi |
+
+
+
+
+
+
+
+ Submit Pengembalian
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Scanner akan otomatis memanggil data.
+
+
+
+ Scan Dengan Kamera
+
+
+
+
+
+
+
+
+ | No |
+ Barcode |
+ Nama Barang |
+ Serial Number |
+ Aksi |
+
+
+
+
+
+
+
+ Submit Barang Dibawa
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | No |
+ Barcode |
+ Nama Barang |
+ Serial Number |
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+ Submit Transfer
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | No |
+ Nama Teknisi |
+ Kode Karyawan |
+ Departemen |
+ Jumlah Item |
+ Aksi |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+