diff --git a/bootstrap.zip b/bootstrap.zip
new file mode 100644
index 0000000..8d36c26
Binary files /dev/null and b/bootstrap.zip differ
diff --git a/index.php b/index.php
new file mode 100644
index 0000000..12bad28
--- /dev/null
+++ b/index.php
@@ -0,0 +1,5 @@
+
diff --git a/routes/db_conn.php b/routes/db_conn.php
new file mode 100644
index 0000000..76c78a1
--- /dev/null
+++ b/routes/db_conn.php
@@ -0,0 +1,16 @@
+
diff --git a/src/img/logoayula.png b/src/img/logoayula.png
new file mode 100644
index 0000000..723ccc1
Binary files /dev/null and b/src/img/logoayula.png differ
diff --git a/src/img/smallest-ayula.png b/src/img/smallest-ayula.png
new file mode 100644
index 0000000..3f651d3
Binary files /dev/null and b/src/img/smallest-ayula.png differ
diff --git a/src/img/userprofile.png b/src/img/userprofile.png
new file mode 100644
index 0000000..f46fcd3
Binary files /dev/null and b/src/img/userprofile.png differ
diff --git a/src/img/warning.png b/src/img/warning.png
new file mode 100644
index 0000000..b50a8d7
Binary files /dev/null and b/src/img/warning.png differ
diff --git a/views/barang/addproduct.php b/views/barang/addproduct.php
new file mode 100644
index 0000000..5a04982
--- /dev/null
+++ b/views/barang/addproduct.php
@@ -0,0 +1,385 @@
+query($query);
+
+ if ($result->num_rows > 0) {
+ // Extract existing code
+ $row = $result->fetch_assoc();
+ $lastCode = $row['kode_barang'];
+
+ // Extract the numeric part and increment
+ $numericPart = intval(substr($lastCode, 3)); // Extract numbers after 'BRG'
+ $nextNumeric = $numericPart + 1;
+
+ // Format with leading zeros (e.g., BRG001, BRG002, etc.)
+ $newCode = 'BRG' . str_pad($nextNumeric, 3, '0', STR_PAD_LEFT);
+ } else {
+ // If no existing codes, start with BRG001
+ $newCode = 'BRG001';
+ }
+
+ return $newCode;
+}
+
+if ($_SERVER["REQUEST_METHOD"] == "POST") {
+ // Generate new product code
+ $kode_barang = generateProductCode($conn);
+
+ // Get data from form
+ $nama_barang = isset($_POST['nama_barang']) ? $_POST['nama_barang'] : '';
+ $id_jenis = isset($_POST['id_jenis']) ? $_POST['id_jenis'] : '';
+ $stok = isset($_POST['stok']) ? $_POST['stok'] : 0;
+ $harga = isset($_POST['harga']) ? $_POST['harga'] : '';
+
+ // Check if image file was uploaded
+ if (isset($_FILES['image']) && $_FILES['image']['error'] == 0) {
+ $image = $_FILES['image'];
+
+ // Validate image
+ $image_name = basename($image['name']);
+ $target_dir = "../uploads/img-barang/"; // Target folder to store images
+ $target_file = $target_dir . $image_name;
+ $image_file_type = strtolower(pathinfo($target_file, PATHINFO_EXTENSION));
+
+ // Check if uploaded file is an image
+ if (in_array($image_file_type, ['jpg', 'jpeg', 'png', 'gif'])) {
+ // Move file to target folder
+ if (!move_uploaded_file($image['tmp_name'], $target_file)) {
+ echo "Error: Failed to upload image!";
+ exit;
+ }
+ } else {
+ echo "Error: Only images (JPG, JPEG, PNG, GIF) are allowed!";
+ exit;
+ }
+ } else {
+ $image_name = ''; // If no image was uploaded, set image name to empty
+ }
+
+ // Validate id_jenis
+ $query_jenis = "SELECT id_jenis FROM jenis_barang WHERE id_jenis = ?";
+ $stmt_jenis = $conn->prepare($query_jenis);
+ $stmt_jenis->bind_param("i", $id_jenis);
+ $stmt_jenis->execute();
+ $stmt_jenis->store_result();
+
+ if ($stmt_jenis->num_rows == 0 && $id_jenis != '') {
+ echo "Error: Category not found!";
+ exit;
+ }
+
+ // Validate stock and price must be numbers
+ if (!preg_match('/^\d+$/', $stok) || !preg_match('/^\d+$/', $harga)) {
+ die("");
+ }
+
+ // Convert to integers for safety
+ $stok = intval($stok);
+ $harga = intval($harga);
+
+ // Insert data into database (now including kode_barang)
+ $sql = "INSERT INTO barang (kode_barang, nama_barang, id_jenis, stok, harga, image)
+ VALUES (?, ?, ?, ?, ?, ?)";
+ $stmt = $conn->prepare($sql);
+ $stmt->bind_param("ssisis", $kode_barang, $nama_barang, $id_jenis, $stok, $harga, $image_name);
+
+ if ($stmt->execute()) {
+ // Redirect with success parameter
+ header("Location: productlist.php?success_add=1");
+ exit();
+}}
+
+// Fetch categories for the dropdown
+$query_categories = "SELECT id_jenis, nama_jenis FROM jenis_barang ORDER BY nama_jenis";
+$result_categories = $conn->query($query_categories);
+
+?>
+
+
+
+
+
+ Tanggal Export: ' . date('d/m/Y H:i:s') . '
+
+ Tanggal Export: ' . date('d/m/Y H:i:s') . '
+
+
+
+
+
+
+
+
+
+
+
+
+
Produk Stok Rendah
+
+ Stok < 10
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Prioritas |
+ ID Barang |
+ Nama Barang |
+ Stok Saat Ini |
+ Harga |
+
+ Aksi |
+
+
+
+ 0.7) {
+ $priority_class = 'priority-high';
+ } elseif ($product['topsis_score'] > 0.4) {
+ $priority_class = 'priority-medium';
+ } else {
+ $priority_class = 'priority-low';
+ }
+ ?>
+
+ |
+ |
+ |
+ |
+ Rp |
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/views/barang/transfer.php b/views/barang/transfer.php
new file mode 100644
index 0000000..1555f12
--- /dev/null
+++ b/views/barang/transfer.php
@@ -0,0 +1,334 @@
+connect_error) {
+ log_debug("Connection failed: " . $conn->connect_error);
+ echo json_encode([
+ 'success' => false,
+ 'message' => 'Koneksi database gagal: ' . $conn->connect_error
+ ]);
+ exit;
+}
+
+log_debug("Database connected successfully");
+
+// Check if we're handling a single product or bulk transfer
+$is_bulk = isset($_POST['product_ids']) && is_array($_POST['product_ids']);
+
+if ($is_bulk) {
+ // Bulk transfer
+ log_debug("Processing bulk transfer");
+
+ $product_ids = $_POST['product_ids'];
+ $quantities = $_POST['quantities'];
+
+ // Validate input
+ if (empty($product_ids) || empty($quantities) || count($product_ids) != count($quantities)) {
+ log_debug("Invalid bulk data");
+ echo json_encode([
+ 'success' => false,
+ 'message' => 'Data tidak valid untuk transfer massal.'
+ ]);
+ exit;
+ }
+
+ // Begin transaction
+ $conn->begin_transaction();
+
+ $success_count = 0;
+ $error_messages = [];
+
+ for ($i = 0; $i < count($product_ids); $i++) {
+ $id_barang = $product_ids[$i];
+ $quantity = intval($quantities[$i]);
+
+ log_debug("Processing item $i: ID=$id_barang, Quantity=$quantity");
+
+ // Skip invalid items
+ if (empty($id_barang) || $quantity <= 0) {
+ $error_messages[] = "Data tidak valid untuk produk #$i";
+ continue;
+ }
+
+ // Get product details
+ $stmt = $conn->prepare("SELECT * FROM barang WHERE id_barang = ?");
+ $stmt->bind_param("s", $id_barang);
+ $stmt->execute();
+ $result = $stmt->get_result();
+
+ if ($result->num_rows == 0) {
+ $error_messages[] = "Produk dengan ID $id_barang tidak ditemukan";
+ $stmt->close();
+ continue;
+ }
+
+ $product = $result->fetch_assoc();
+
+ // Check stock
+ if ($quantity > $product['stok']) {
+ $error_messages[] = "Stok tidak mencukupi untuk produk {$product['nama_barang']}";
+ $stmt->close();
+ continue;
+ }
+
+ // Update stock in barang
+ $new_stock = $product['stok'] - $quantity;
+ $update_stmt = $conn->prepare("UPDATE barang SET stok = ? WHERE id_barang = ?");
+ $update_stmt->bind_param("is", $new_stock, $id_barang);
+
+ if (!$update_stmt->execute()) {
+ $error_messages[] = "Gagal memperbarui stok untuk produk {$product['nama_barang']}";
+ $stmt->close();
+ $update_stmt->close();
+ continue;
+ }
+
+ $update_stmt->close();
+
+ // Check if exists in barang_kasir
+ $check_stmt = $conn->prepare("SELECT * FROM barang_kasir WHERE kode_barang = ?");
+ $check_stmt->bind_param("s", $product['kode_barang']);
+ $check_stmt->execute();
+ $check_result = $check_stmt->get_result();
+
+ $timestamp = date('Y-m-d H:i:s');
+
+ if ($check_result->num_rows > 0) {
+ // Update existing record
+ $kasir_product = $check_result->fetch_assoc();
+ $new_kasir_stock = $kasir_product['stok'] + $quantity;
+
+ $kasir_update_sql = "UPDATE barang_kasir SET stok = ?, update_at = ? WHERE id_barangK = ?";
+ $kasir_update_stmt = $conn->prepare($kasir_update_sql);
+ $kasir_update_stmt->bind_param("isi", $new_kasir_stock, $timestamp, $kasir_product['id_barangK']);
+
+ if (!$kasir_update_stmt->execute()) {
+ $error_messages[] = "Gagal memperbarui stok di kasir untuk produk {$product['nama_barang']}";
+ $stmt->close();
+ $check_stmt->close();
+ $kasir_update_stmt->close();
+ continue;
+ }
+
+ $kasir_update_stmt->close();
+ } else {
+ // Insert new record
+ // Using direct query to avoid parameter binding issues
+ $kode_barang = $conn->real_escape_string($product['kode_barang']);
+ $nama_barang = $conn->real_escape_string($product['nama_barang']);
+ $id_jenis = (int)$product['id_jenis'];
+ $harga = $conn->real_escape_string($product['harga']);
+ $stok = (int)$quantity;
+ $gambar = $conn->real_escape_string($product['image'] ?? '');
+ $timestamp_esc = $conn->real_escape_string($timestamp);
+
+ $insert_query = "INSERT INTO barang_kasir (kode_barang, nama_barang, id_jenis, harga, stok, gambar, created_at, update_at)
+ VALUES ('$kode_barang', '$nama_barang', $id_jenis, '$harga', $stok, '$gambar', '$timestamp_esc', '$timestamp_esc')";
+
+ if (!$conn->query($insert_query)) {
+ $error_messages[] = "Gagal menambahkan produk {$product['nama_barang']} ke kasir";
+ $stmt->close();
+ $check_stmt->close();
+ continue;
+ }
+ }
+
+ $check_stmt->close();
+ $stmt->close();
+ $success_count++;
+ }
+
+ if ($success_count == 0) {
+ // If no products were successfully processed
+ $conn->rollback();
+ log_debug("Bulk transfer failed: " . implode(', ', $error_messages));
+ echo json_encode([
+ 'success' => false,
+ 'message' => 'Tidak ada produk yang berhasil dipindahkan: ' . implode(', ', $error_messages)
+ ]);
+ } else {
+ // Commit if at least one product was processed
+ $conn->commit();
+
+ if (count($error_messages) > 0) {
+ log_debug("Partial bulk transfer success: $success_count items, with errors: " . implode(', ', $error_messages));
+ echo json_encode([
+ 'success' => true,
+ 'message' => "Berhasil memindahkan $success_count produk ke kasir. Beberapa produk gagal: " . implode(', ', $error_messages)
+ ]);
+ } else {
+ log_debug("Complete bulk transfer success: $success_count items");
+ echo json_encode([
+ 'success' => true,
+ 'message' => "Berhasil memindahkan $success_count produk ke kasir."
+ ]);
+ }
+ }
+} else {
+ // Single product transfer
+ if (!isset($_POST['id_barang']) || !isset($_POST['quantity'])) {
+ log_debug("Missing required parameters for single transfer");
+ echo json_encode([
+ 'success' => false,
+ 'message' => 'Parameter tidak lengkap'
+ ]);
+ exit;
+ }
+
+ $id_barang = $_POST['id_barang'];
+ $quantity = (int)$_POST['quantity'];
+
+ log_debug("Processing single transfer for product ID: $id_barang, quantity: $quantity");
+
+ // Validate data
+ if (empty($id_barang) || $quantity <= 0) {
+ log_debug("Invalid parameters");
+ echo json_encode([
+ 'success' => false,
+ 'message' => 'Parameter tidak valid'
+ ]);
+ exit;
+ }
+
+ // Get product information
+ $stmt = $conn->prepare("SELECT * FROM barang WHERE id_barang = ?");
+ $stmt->bind_param("s", $id_barang);
+ $stmt->execute();
+ $result = $stmt->get_result();
+
+ if ($result->num_rows == 0) {
+ log_debug("Product not found");
+ echo json_encode([
+ 'success' => false,
+ 'message' => 'Barang tidak ditemukan'
+ ]);
+ $stmt->close();
+ exit;
+ }
+
+ $product = $result->fetch_assoc();
+ log_debug("Product found: " . json_encode($product));
+
+ // Check if enough stock
+ if ($quantity > $product['stok']) {
+ log_debug("Insufficient stock");
+ echo json_encode([
+ 'success' => false,
+ 'message' => 'Stok tidak mencukupi'
+ ]);
+ $stmt->close();
+ exit;
+ }
+
+ // Begin transaction
+ $conn->begin_transaction();
+
+ try {
+ // 1. Update stock in barang
+ $new_stock = $product['stok'] - $quantity;
+ $update_stmt = $conn->prepare("UPDATE barang SET stok = ? WHERE id_barang = ?");
+ $update_stmt->bind_param("is", $new_stock, $id_barang);
+
+ if (!$update_stmt->execute()) {
+ throw new Exception("Failed to update product stock: " . $update_stmt->error);
+ }
+
+ log_debug("Updated barang stock to: $new_stock");
+ $update_stmt->close();
+
+ // 2. Check if product exists in barang_kasir
+ $check_stmt = $conn->prepare("SELECT * FROM barang_kasir WHERE kode_barang = ?");
+ $check_stmt->bind_param("s", $product['kode_barang']);
+ $check_stmt->execute();
+ $check_result = $check_stmt->get_result();
+
+ $timestamp = date('Y-m-d H:i:s');
+
+ if ($check_result->num_rows > 0) {
+ // 3a. Update existing product
+ $kasir_product = $check_result->fetch_assoc();
+ $new_kasir_stock = $kasir_product['stok'] + $quantity;
+
+ log_debug("Product exists in cashier, updating stock to: $new_kasir_stock");
+
+ $kasir_update_stmt = $conn->prepare("UPDATE barang_kasir SET stok = ?, update_at = ? WHERE id_barangK = ?");
+ $kasir_update_stmt->bind_param("isi", $new_kasir_stock, $timestamp, $kasir_product['id_barangK']);
+
+ if (!$kasir_update_stmt->execute()) {
+ throw new Exception("Failed to update cashier stock: " . $kasir_update_stmt->error);
+ }
+
+ $kasir_update_stmt->close();
+ } else {
+ // 3b. Insert new product with direct query
+ log_debug("Product doesn't exist in cashier, inserting new record");
+
+ // Set default empty string for image
+ $kode_barang = $conn->real_escape_string($product['kode_barang']);
+ $nama_barang = $conn->real_escape_string($product['nama_barang']);
+ $id_jenis = (int)$product['id_jenis'];
+ $harga = $conn->real_escape_string($product['harga']);
+ $stok = (int)$quantity;
+ $gambar = $conn->real_escape_string($product['image'] ?? '');
+ $timestamp_esc = $conn->real_escape_string($timestamp);
+
+ $insert_query = "INSERT INTO barang_kasir (kode_barang, nama_barang, id_jenis, harga, stok, gambar, created_at, update_at)
+ VALUES ('$kode_barang', '$nama_barang', $id_jenis, '$harga', $stok, '$gambar', '$timestamp_esc', '$timestamp_esc')";
+
+ log_debug("Insert query: $insert_query");
+
+ if (!$conn->query($insert_query)) {
+ throw new Exception("Failed to insert product into cashier: " . $conn->error);
+ }
+ }
+
+ $check_stmt->close();
+
+ // Commit transaction
+ $conn->commit();
+
+ log_debug("Transfer completed successfully");
+
+ echo json_encode([
+ 'success' => true,
+ 'message' => 'Berhasil memindahkan ' . $quantity . ' item ke kasir'
+ ]);
+
+ } catch (Exception $e) {
+ // Rollback on error
+ $conn->rollback();
+
+ log_debug("Error occurred: " . $e->getMessage());
+
+ echo json_encode([
+ 'success' => false,
+ 'message' => 'Terjadi kesalahan: ' . $e->getMessage()
+ ]);
+ }
+
+ $stmt->close();
+}
+
+$conn->close();
+log_debug('Script completed');
+?>
\ No newline at end of file
diff --git a/views/barang/transfer_debug.log b/views/barang/transfer_debug.log
new file mode 100644
index 0000000..8f6717e
--- /dev/null
+++ b/views/barang/transfer_debug.log
@@ -0,0 +1,52 @@
+[2025-06-01 09:32:55] Script started
+[2025-06-01 09:32:55] POST data: {"id_barang":"120","quantity":"1"}
+[2025-06-01 09:32:55] Database connected successfully
+[2025-06-01 09:32:55] Processing single transfer for product ID: 120, quantity: 1
+[2025-06-01 09:32:55] Product found: {"id_barang":120,"kode_barang":"BRG039","nama_barang":"Produk A","stok":5,"harga":"150000","id_jenis":8,"image":"kios-Halaman-2.drawio (2).png"}
+[2025-06-01 09:32:55] Updated barang stock to: 4
+[2025-06-01 09:32:55] Product exists in cashier, updating stock to: 6
+[2025-06-01 09:32:55] Transfer completed successfully
+[2025-06-01 09:32:55] Script completed
+[2025-06-01 09:33:23] Script started
+[2025-06-01 09:33:23] POST data: {"product_ids":["120","121"],"quantities":["1","1"]}
+[2025-06-01 09:33:23] Database connected successfully
+[2025-06-01 09:33:23] Processing bulk transfer
+[2025-06-01 09:33:23] Processing item 0: ID=120, Quantity=1
+[2025-06-01 09:33:23] Processing item 1: ID=121, Quantity=1
+[2025-06-01 09:33:23] Complete bulk transfer success: 2 items
+[2025-06-01 09:33:23] Script completed
+[2025-06-01 09:33:33] Script started
+[2025-06-01 09:33:33] POST data: {"id_barang":"122","quantity":"1"}
+[2025-06-01 09:33:33] Database connected successfully
+[2025-06-01 09:33:33] Processing single transfer for product ID: 122, quantity: 1
+[2025-06-01 09:33:33] Product found: {"id_barang":122,"kode_barang":"BRG041","nama_barang":"Produk B","stok":12,"harga":"75000","id_jenis":1,"image":"kios-Halaman-3.drawio (2).png"}
+[2025-06-01 09:33:33] Updated barang stock to: 11
+[2025-06-01 09:33:33] Product exists in cashier, updating stock to: 13
+[2025-06-01 09:33:33] Transfer completed successfully
+[2025-06-01 09:33:33] Script completed
+[2025-06-01 21:07:57] Script started
+[2025-06-01 21:07:57] POST data: {"id_barang":"120","quantity":"1"}
+[2025-06-01 21:07:57] Database connected successfully
+[2025-06-01 21:07:57] Processing single transfer for product ID: 120, quantity: 1
+[2025-06-01 21:07:57] Product found: {"id_barang":120,"kode_barang":"BRG039","nama_barang":"Produk A","stok":3,"harga":"150000","id_jenis":8,"image":"product_1748803380.png"}
+[2025-06-01 21:07:57] Updated barang stock to: 2
+[2025-06-01 21:07:57] Product exists in cashier, updating stock to: 8
+[2025-06-01 21:07:57] Transfer completed successfully
+[2025-06-01 21:07:57] Script completed
+[2025-06-01 21:08:07] Script started
+[2025-06-01 21:08:07] POST data: {"product_ids":["122","124"],"quantities":["1","1"]}
+[2025-06-01 21:08:07] Database connected successfully
+[2025-06-01 21:08:07] Processing bulk transfer
+[2025-06-01 21:08:07] Processing item 0: ID=122, Quantity=1
+[2025-06-01 21:08:07] Processing item 1: ID=124, Quantity=1
+[2025-06-01 21:08:07] Complete bulk transfer success: 2 items
+[2025-06-01 21:08:07] Script completed
+[2025-06-03 03:21:32] Script started
+[2025-06-03 03:21:32] POST data: {"id_barang":"122","quantity":"1"}
+[2025-06-03 03:21:32] Database connected successfully
+[2025-06-03 03:21:32] Processing single transfer for product ID: 122, quantity: 1
+[2025-06-03 03:21:32] Product found: {"id_barang":122,"kode_barang":"BRG041","nama_barang":"Produk B","stok":10,"harga":"75000","id_jenis":1,"image":"kios-Halaman-3.drawio (2).png"}
+[2025-06-03 03:21:32] Updated barang stock to: 9
+[2025-06-03 03:21:32] Product exists in cashier, updating stock to: 15
+[2025-06-03 03:21:32] Transfer completed successfully
+[2025-06-03 03:21:32] Script completed
diff --git a/views/barang/transfer_handler.php b/views/barang/transfer_handler.php
new file mode 100644
index 0000000..8a6ab6b
--- /dev/null
+++ b/views/barang/transfer_handler.php
@@ -0,0 +1,216 @@
+connect_error) {
+ die(json_encode([
+ 'success' => false,
+ 'message' => "Koneksi gagal: " . $conn->connect_error
+ ]));
+}
+
+// Fungsi untuk memvalidasi data yang diterima
+function validateData($data) {
+ $data = trim($data);
+ $data = stripslashes($data);
+ $data = htmlspecialchars($data);
+ return $data;
+}
+
+// Handle single product transfer
+if (isset($_POST['action']) && $_POST['action'] == 'transfer_product') {
+ // Ambil data dari POST
+ $id_barang = validateData($_POST['id_barang']);
+ $quantity = intval($_POST['quantity']);
+
+ // Validasi input
+ if (empty($id_barang) || $quantity <= 0) {
+ echo json_encode([
+ 'success' => false,
+ 'message' => 'Data tidak valid.'
+ ]);
+ exit;
+ }
+
+ // Check if stock is sufficient
+ $sql = "SELECT stok FROM barang WHERE id_barang = ?";
+ $stmt = $conn->prepare($sql);
+ $stmt->bind_param("s", $id_barang);
+ $stmt->execute();
+ $result = $stmt->get_result();
+
+ if ($result->num_rows == 0) {
+ echo json_encode([
+ 'success' => false,
+ 'message' => 'Barang tidak ditemukan.'
+ ]);
+ $stmt->close();
+ exit;
+ }
+
+ $row = $result->fetch_assoc();
+ $current_stock = $row['stok'];
+
+ if ($quantity > $current_stock) {
+ echo json_encode([
+ 'success' => false,
+ 'message' => 'Jumlah melebihi stok tersedia.'
+ ]);
+ $stmt->close();
+ exit;
+ }
+
+ // Begin transaction
+ $conn->begin_transaction();
+
+ try {
+ // Update stock in barang table
+ $new_stock = $current_stock - $quantity;
+ $update_sql = "UPDATE barang SET stok = ? WHERE id_barang = ?";
+ $update_stmt = $conn->prepare($update_sql);
+ $update_stmt->bind_param("is", $new_stock, $id_barang);
+ $update_stmt->execute();
+
+ // TODO: Insert data to cashier table or perform other actions as needed
+ // This will depend on your specific requirements for transferring to cashier
+
+ // Commit the transaction
+ $conn->commit();
+
+ echo json_encode([
+ 'success' => true,
+ 'message' => 'Berhasil memindahkan ' . $quantity . ' item ke kasir.'
+ ]);
+
+ } catch (Exception $e) {
+ // Rollback transaction on error
+ $conn->rollback();
+
+ echo json_encode([
+ 'success' => false,
+ 'message' => 'Terjadi kesalahan: ' . $e->getMessage()
+ ]);
+ }
+
+ $stmt->close();
+ if (isset($update_stmt)) {
+ $update_stmt->close();
+ }
+}
+
+// Handle bulk product transfers
+if (isset($_POST['action']) && $_POST['action'] == 'bulk_transfer_products') {
+ // Get product IDs and quantities
+ $product_ids = isset($_POST['product_ids']) ? $_POST['product_ids'] : [];
+ $quantities = isset($_POST['quantities']) ? $_POST['quantities'] : [];
+
+ // Validate input
+ if (empty($product_ids) || empty($quantities) || count($product_ids) != count($quantities)) {
+ echo json_encode([
+ 'success' => false,
+ 'message' => 'Data tidak valid.'
+ ]);
+ exit;
+ }
+
+ // Begin transaction
+ $conn->begin_transaction();
+
+ try {
+ $success_count = 0;
+ $error_messages = [];
+
+ // Process each product
+ for ($i = 0; $i < count($product_ids); $i++) {
+ $id_barang = validateData($product_ids[$i]);
+ $quantity = intval($quantities[$i]);
+
+ if (empty($id_barang) || $quantity <= 0) {
+ $error_messages[] = "Data tidak valid untuk produk #$i";
+ continue;
+ }
+
+ // Check if stock is sufficient
+ $sql = "SELECT stok FROM barang WHERE id_barang = ?";
+ $stmt = $conn->prepare($sql);
+ $stmt->bind_param("s", $id_barang);
+ $stmt->execute();
+ $result = $stmt->get_result();
+
+ if ($result->num_rows == 0) {
+ $error_messages[] = "Barang dengan ID $id_barang tidak ditemukan";
+ $stmt->close();
+ continue;
+ }
+
+ $row = $result->fetch_assoc();
+ $current_stock = $row['stok'];
+
+ if ($quantity > $current_stock) {
+ $error_messages[] = "Jumlah melebihi stok tersedia untuk produk ID $id_barang";
+ $stmt->close();
+ continue;
+ }
+
+ // Update stock in barang table
+ $new_stock = $current_stock - $quantity;
+ $update_sql = "UPDATE barang SET stok = ? WHERE id_barang = ?";
+ $update_stmt = $conn->prepare($update_sql);
+ $update_stmt->bind_param("is", $new_stock, $id_barang);
+ $update_stmt->execute();
+ $update_stmt->close();
+ $stmt->close();
+
+ // TODO: Insert data to cashier table or perform other actions as needed
+ // This will depend on your specific requirements for transferring to cashier
+
+ $success_count++;
+ }
+
+ if ($success_count == 0) {
+ // If no products were successfully processed, rollback and return error
+ $conn->rollback();
+ echo json_encode([
+ 'success' => false,
+ 'message' => 'Tidak ada produk yang berhasil dipindahkan. ' . implode('; ', $error_messages)
+ ]);
+ } else {
+ // Commit the transaction if at least one product was successfully processed
+ $conn->commit();
+
+ if (count($error_messages) > 0) {
+ echo json_encode([
+ 'success' => true,
+ 'message' => "Berhasil memindahkan $success_count produk ke kasir. Beberapa produk gagal: " . implode('; ', $error_messages)
+ ]);
+ } else {
+ echo json_encode([
+ 'success' => true,
+ 'message' => "Berhasil memindahkan $success_count produk ke kasir."
+ ]);
+ }
+ }
+
+ } catch (Exception $e) {
+ // Rollback transaction on error
+ $conn->rollback();
+
+ echo json_encode([
+ 'success' => false,
+ 'message' => 'Terjadi kesalahan: ' . $e->getMessage()
+ ]);
+ }
+}
+
+// Close connection
+$conn->close();
+?>
\ No newline at end of file
diff --git a/views/barang/updateproduct.php b/views/barang/updateproduct.php
new file mode 100644
index 0000000..d9fb12e
--- /dev/null
+++ b/views/barang/updateproduct.php
@@ -0,0 +1,60 @@
+
diff --git a/views/dashboard/index.php b/views/dashboard/index.php
new file mode 100644
index 0000000..d430b96
--- /dev/null
+++ b/views/dashboard/index.php
@@ -0,0 +1,703 @@
+query($query);
+ $data['today_transactions'] = ($result->num_rows > 0) ? $result->fetch_assoc()['count'] : 0;
+
+ // Today's items sold
+ $query = "SELECT SUM(total_item) as count FROM transaksi WHERE DATE(tanggal) = '$today'";
+ $result = $conn->query($query);
+ $data['today_items'] = ($result->num_rows > 0) ? $result->fetch_assoc()['count'] : 0;
+ if ($data['today_items'] === NULL) $data['today_items'] = 0;
+
+ // This week's transactions
+ $startOfWeek = date('Y-m-d', strtotime('this week monday'));
+ $query = "SELECT COUNT(*) as count FROM transaksi WHERE tanggal BETWEEN '$startOfWeek' AND '$today'";
+ $result = $conn->query($query);
+ $data['week_transactions'] = ($result->num_rows > 0) ? $result->fetch_assoc()['count'] : 0;
+
+ // Total products in inventory
+ $query = "SELECT COUNT(*) as count FROM barang";
+ $result = $conn->query($query);
+ $data['total_products'] = ($result->num_rows > 0) ? $result->fetch_assoc()['count'] : 0;
+
+ // Count low stock items (stok <= 10)
+ $query = "SELECT COUNT(*) as count FROM barang WHERE stok <= 10";
+ $result = $conn->query($query);
+ $data['low_stock_count'] = ($result->num_rows > 0) ? $result->fetch_assoc()['count'] : 0;
+
+ return $data;
+}
+
+// Function to get recent transactions
+function getRecentTransactions($conn, $limit = 5) {
+ $query = "SELECT t.kode_transaksi, t.tanggal, t.total_item, t.status
+ FROM transaksi t
+ ORDER BY t.tanggal DESC
+ LIMIT $limit";
+
+ $result = $conn->query($query);
+ $transactions = array();
+
+ if ($result->num_rows > 0) {
+ while($row = $result->fetch_assoc()) {
+ $transactions[] = $row;
+ }
+ }
+
+ return $transactions;
+}
+
+// Function to get popular products - FIXED to ensure it gets the most recent data
+function getPopularProducts($conn, $limit = 10) {
+ // Use current date to ensure we get the latest data
+ $today = date('Y-m-d');
+ $startOfWeek = date('Y-m-d', strtotime('this week monday'));
+
+ // Modified query to join with detail_transaksi directly on id_transaksi
+ $query = "SELECT b.kode_barang, b.nama_barang, SUM(dt.jumlah) as total_quantity
+ FROM detail_transaksi dt
+ JOIN transaksi t ON dt.id_transaksi = t.id_transaksi
+ JOIN barang b ON dt.id_barang = b.id_barang
+ WHERE t.tanggal BETWEEN '$startOfWeek' AND NOW()
+ GROUP BY b.id_barang
+ ORDER BY total_quantity DESC
+ LIMIT $limit";
+
+ $result = $conn->query($query);
+ $products = array();
+
+ if ($result && $result->num_rows > 0) {
+ while($row = $result->fetch_assoc()) {
+ $products[] = $row;
+ }
+ }
+
+ return $products;
+}
+
+// Function to get low stock items
+function getLowStockItems($conn, $threshold = 10, $limit = 5) {
+ $query = "SELECT kode_barang, nama_barang, stok
+ FROM barang
+ WHERE stok <= $threshold
+ ORDER BY stok ASC
+ LIMIT $limit";
+
+ $result = $conn->query($query);
+ $lowStockItems = array();
+
+ if ($result->num_rows > 0) {
+ while($row = $result->fetch_assoc()) {
+ $lowStockItems[] = $row;
+ }
+ }
+
+ return $lowStockItems;
+}
+
+// Get dashboard data
+$dashboardData = getEmployeeDashboardData($conn);
+$recentTransactions = getRecentTransactions($conn);
+$popularProducts = getPopularProducts($conn);
+$lowStockItems = getLowStockItems($conn);
+
+// Get user activity count (simplified)
+$userActivityQuery = "SELECT COUNT(*) as count FROM transaksi WHERE tanggal > DATE_SUB(NOW(), INTERVAL 30 DAY)";
+$userActivityResult = $conn->query($userActivityQuery);
+$userActivity = ($userActivityResult->num_rows > 0) ? $userActivityResult->fetch_assoc()['count'] : 0;
+
+// Recent products
+$recentProductsQuery = "SELECT kode_barang, nama_barang, harga FROM barang ORDER BY created_at DESC LIMIT 5";
+$recentProductsResult = $conn->query($recentProductsQuery);
+$recentProducts = array();
+if ($recentProductsResult->num_rows > 0) {
+ while($row = $recentProductsResult->fetch_assoc()) {
+ $recentProducts[] = $row;
+ }
+}
+
+// Close database connection
+$conn->close();
+?>
+
+
+
+
+
+
+
+
+
+
+
Ayula Store - Dashboard
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Selamat Datang, !
+
Ringkasan aktivitas toko hari ini,
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Transaksi Hari Ini
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Item Terjual Hari Ini
+
+ Total unit terjual
+
+
+
+
+
+
+
+
+
+
+
+
Transaksi Minggu Ini
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Produk Stok Menipis
+
+ Perlu perhatian
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0): ?>
+
+
+
+
+
Belum ada transaksi hari ini
+
Transaksi baru akan muncul di sini
+
+
+
+
+
+
+
+
+
+
+
+ 0): ?>
+
+
+
+
+ Kode |
+ Nama Produk |
+ Stok |
+ Status |
+
+
+
+
+
+ |
+ |
+ |
+
+
+ Kritis
+
+ Menipis
+
+ |
+
+
+
+
+
+
+
+
+
Semua Produk Memiliki Stok Memadai
+
Tidak ada produk yang stoknya menipis saat ini
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0): ?>
+
+
+
+
+
+
+
+
+
+
+ Total Terjual:
+ unit
+
+
+
+
+
+
+
+
+ 0): ?>
+
+
+
+
+
+
+ Total Terjual:
+ unit
+
+
+
+
+
+
+
+
+
+
+
+
Belum Ada Data Penjualan
+
Produk terlaris akan ditampilkan saat ada penjualan
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/views/forgot-password.php b/views/forgot-password.php
new file mode 100644
index 0000000..1c7244a
--- /dev/null
+++ b/views/forgot-password.php
@@ -0,0 +1,118 @@
+ 0) {
+ // Query untuk memperbarui password di database
+ $query = "UPDATE kasir SET password = '$hashed_password' WHERE username = '$username'";
+
+ if (mysqli_query($conn, $query)) {
+ // Jika berhasil, alihkan ke halaman login
+ $_SESSION['username'] = $username;
+ header("Location: index.php");
+ exit();
+ } else {
+ $error_message = "Gagal memperbarui password!";
+ }
+ } else {
+ $error_message = "Username tidak ditemukan!";
+ }
+ }
+}
+?>
+
+
+
+
+
+
+
+
+
+
+
+
Reset Password - Ayula Store
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Reset Password
+ Enter your new password below.
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+
+
+
diff --git a/views/index.php b/views/index.php
new file mode 100644
index 0000000..df366e1
--- /dev/null
+++ b/views/index.php
@@ -0,0 +1,278 @@
+ 0) {
+ $user = mysqli_fetch_assoc($result);
+
+ // Verifikasi password menggunakan password_verify()
+ if (password_verify($password, $user['password'])) {
+ // Jika password valid, login berhasil
+ $_SESSION['user_id'] = $user['id_kasir'];
+ $_SESSION['username'] = $user['username'];
+ $_SESSION['role'] = $user['role'];
+
+ // Jika role adalah admin, tampilkan modal pilihan
+ if ($user['role'] == 'admin') {
+ // Set flag untuk menampilkan modal
+ $showRoleModal = true;
+ } else {
+ // Jika bukan admin, redirect langsung ke dashboard
+ header("Location: /ayula-store/views/dashboard/");
+ exit();
+ }
+ } else {
+ // Jika password salah
+ $error_message = "Password salah!";
+ }
+ } else {
+ // Jika username tidak ditemukan
+ $error_message = "Username tidak ditemukan!";
+ }
+}
+
+// Proses pilihan role dari modal
+if (isset($_POST['role_choice'])) {
+ if ($_POST['role_choice'] == 'gudang') {
+ header("Location: /ayula-store/views/reporttt/report.php");
+ exit();
+ } else if ($_POST['role_choice'] == 'kasir') {
+ header("Location: /ayula-store/views/dashboard/");
+ exit();
+ }
+}
+
+// Proses logout jika tombol close ditekan
+if (isset($_POST['logout'])) {
+ // Hapus semua data session
+ session_unset();
+ session_destroy();
+ // Redirect ke halaman login
+ header("Location: index.php");
+ exit();
+}
+?>
+
+
+
+
+
+
+
+
+
+
+
Login - Ayula Store
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Masuk
+ Silakan masuk ke akun Anda
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+
+
+
+
Selamat datang, !
Silakan pilih akses yang ingin Anda gunakan:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/views/logout.php b/views/logout.php
new file mode 100644
index 0000000..f8f15d7
--- /dev/null
+++ b/views/logout.php
@@ -0,0 +1,24 @@
+
\ No newline at end of file
diff --git a/views/register.php b/views/register.php
new file mode 100644
index 0000000..4eac3d5
--- /dev/null
+++ b/views/register.php
@@ -0,0 +1,110 @@
+ 0) {
+ $error_message = "Username sudah terdaftar!";
+ } else {
+ // Query untuk memasukkan data baru ke dalam database
+ $query = "INSERT INTO kasir (username, password, phone, role) VALUES ('$username', '$hashed_password', '$phone', 'user')";
+
+ if (mysqli_query($conn, $query)) {
+ // Jika berhasil, alihkan ke halaman login
+ $_SESSION['username'] = $username;
+ header("Location: index.php");
+ exit();
+ } else {
+ $error_message = "Gagal melakukan registrasi!";
+ }
+ }
+}
+?>
+
+
+
+
+
+
+
+
+
+
+
Register - Ayula Store
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Create an Account
+ Continue where you left off
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+
+
diff --git a/views/report-issue/index.php b/views/report-issue/index.php
new file mode 100644
index 0000000..9b72589
--- /dev/null
+++ b/views/report-issue/index.php
@@ -0,0 +1,384 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
Ayula Store POS - Laporkan Masalah
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Isi formulir di bawah ini untuk melaporkan masalah. Pesan akan dikirim langsung ke developer melalui WhatsApp.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/views/report/popular-products/index.php b/views/report/popular-products/index.php
new file mode 100644
index 0000000..684d9c4
--- /dev/null
+++ b/views/report/popular-products/index.php
@@ -0,0 +1,949 @@
+ strtotime($endDate)) {
+ $temp = $startDate;
+ $startDate = $endDate;
+ $endDate = $temp;
+}
+
+// Default limit for top products
+$limit = isset($_GET['limit']) ? (int)$_GET['limit'] : 20;
+if ($limit < 5) $limit = 5;
+if ($limit > 100) $limit = 100;
+
+// Default sort type (quantity or revenue)
+$sortBy = isset($_GET['sort_by']) ? $_GET['sort_by'] : 'quantity';
+if (!in_array($sortBy, ['quantity', 'revenue'])) {
+ $sortBy = 'quantity';
+}
+
+// Get the popular products based on user role
+$queryData = getPopularProducts($startDate, $endDate, $limit, $sortBy, $isAdmin);
+$result = $queryData['result'];
+$summary = $queryData['summary'];
+$categories = $queryData['categories'];
+
+// Handle PDF export request if admin
+if (isset($_POST['export_pdf']) || (isset($_GET['export']) && $_GET['export'] == 'pdf')) {
+ exportToPDF($startDate, $endDate, $limit, $sortBy, $isAdmin);
+}
+?>
+
+
+
+
+
+
+
+
+
+
+
+
Ayula Store POS - Produk Terlaris
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Memuat data produk terlaris...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Rentang Tanggal: sampai
+
+
+
+
+
+
+
+
+
+
+ 0): ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Tidak ada data penjualan produk untuk periode yang dipilih
+
+
+ Tidak ada transaksi penjualan yang tercatat untuk tahun lalu ().
+
+ Coba pilih rentang tanggal atau preset tanggal yang berbeda.
+
+
+
+ Reset Filter
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Kode:
+
+
+
+ Terjual:
+ unit
+
+
+
+ Harga:
+ Rp.
+
+
+ Total:
+ Rp.
+
+
+
+
+
+
+ % dari total penjualan
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Rank |
+ Kode |
+ Nama Produk |
+ Kategori |
+ Jumlah Terjual |
+
+ Harga Satuan |
+ Total Pendapatan |
+
+ % dari Total |
+
+
+
+
+
+
+
+ #
+
+
+
+ |
+ |
+ |
+ |
+ |
+
+ Rp. |
+ Rp. |
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Total Produk Terjual:
+ produk
+
+
+ Total Unit Terjual:
+ unit
+
+
+
+
+ Rata-rata Per Hari:
+ 0 ? $summary['total_items_sold'] / $daysDiff : 0;
+ echo number_format($dailyAvgUnits, 1);
+ ?> unit/hari
+
+
+ Pendapatan Rata-rata Per Hari:
+ Rp. 0 ? $summary['total_revenue'] / $daysDiff : 0;
+ echo number_format($dailyAvgRevenue);
+ ?>
+
+
+
+
+ Total Pendapatan:
+ Rp.
+
+
+ Rata-rata Pendapatan Per Produk:
+ Rp. 0 ? $summary['total_revenue'] / $summary['total_products'] : 0;
+ echo number_format($avgRevenuePerProduct);
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Untuk laporan keuangan terperinci atau untuk mengekspor data, silakan hubungi administrator Anda.
+
+
+
+
+
+
+ Produk Dilihat:
+ produk teratas
+
+
+ Total Unit Terjual (Diproses):
+ unit
+
+
+
+
+ Laporan Dibuat:
+
+
+
+ Level Akses:
+ Karyawan
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Fitur Dibatasi
+
+
Fitur ini hanya tersedia untuk administrator dan personel yang berwenang.
+
Akun karyawan tidak memiliki akses ke fungsi cetak atau ekspor.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/views/report/popular-products/popular-products-config.php b/views/report/popular-products/popular-products-config.php
new file mode 100644
index 0000000..7977b6d
--- /dev/null
+++ b/views/report/popular-products/popular-products-config.php
@@ -0,0 +1,421 @@
+ [
+ 'label' => 'Hari Ini',
+ 'start' => date('Y-m-d'),
+ 'end' => date('Y-m-d')
+ ],
+ 'yesterday' => [
+ 'label' => 'Kemarin',
+ 'start' => date('Y-m-d', strtotime('-1 day')),
+ 'end' => date('Y-m-d', strtotime('-1 day'))
+ ],
+ 'this_week' => [
+ 'label' => 'Minggu Ini',
+ 'start' => date('Y-m-d', strtotime('monday this week')),
+ 'end' => date('Y-m-d')
+ ],
+ 'last_week' => [
+ 'label' => 'Minggu Lalu',
+ 'start' => date('Y-m-d', strtotime('monday last week')),
+ 'end' => date('Y-m-d', strtotime('sunday last week'))
+ ],
+ 'this_month' => [
+ 'label' => 'Bulan Ini',
+ 'start' => date('Y-m-01'),
+ 'end' => date('Y-m-d')
+ ],
+ ];
+
+ // Admin-only presets
+ if ($isAdmin) {
+ $presets['last_month'] = [
+ 'label' => 'Bulan Lalu',
+ 'start' => date('Y-m-d', strtotime('first day of last month')),
+ 'end' => date('Y-m-d', strtotime('last day of last month'))
+ ];
+
+ $presets['last_90_days'] = [
+ 'label' => '90 Hari Terakhir',
+ 'start' => date('Y-m-d', strtotime('-90 days')),
+ 'end' => date('Y-m-d')
+ ];
+
+ $presets['this_year'] = [
+ 'label' => 'Tahun Ini',
+ 'start' => date('Y') . '-01-01',
+ 'end' => date('Y-m-d')
+ ];
+
+ $presets['all_time'] = [
+ 'label' => 'Sepanjang Waktu',
+ 'start' => '2000-01-01', // Set a reasonable start date
+ 'end' => date('Y-m-d')
+ ];
+ }
+
+ return $presets;
+}
+
+// Function to check if an employee has permission for a specific report feature
+function canAccessFeature($feature) {
+ // Default permissions based on role
+ $permissions = [
+ 'admin' => [
+ 'export_excel' => true,
+ 'export_pdf' => true,
+ 'print_report' => true,
+ 'view_financial' => true,
+ 'view_payment_methods' => true,
+ 'view_all_time' => true,
+ 'view_summary' => true,
+ 'view_popular_products' => true
+ ],
+ 'manager' => [
+ 'export_excel' => true,
+ 'export_pdf' => false,
+ 'print_report' => true,
+ 'view_financial' => true,
+ 'view_payment_methods' => true,
+ 'view_all_time' => false,
+ 'view_summary' => true,
+ 'view_popular_products' => true
+ ],
+ 'employee' => [
+ 'export_excel' => false,
+ 'export_pdf' => false,
+ 'print_report' => false,
+ 'view_financial' => false,
+ 'view_payment_methods' => false,
+ 'view_all_time' => false,
+ 'view_summary' => false,
+ 'view_popular_products' => false
+ ],
+ 'user' => [
+ 'export_excel' => false,
+ 'export_pdf' => false,
+ 'print_report' => false,
+ 'view_financial' => false,
+ 'view_payment_methods' => false,
+ 'view_all_time' => false,
+ 'view_summary' => false,
+ 'view_popular_products' => false
+ ]
+ ];
+
+ $role = getUserRole();
+
+ // If role doesn't exist in our permissions, default to employee
+ if (!isset($permissions[$role])) {
+ $role = 'employee';
+ }
+
+ // Return permission status
+ return isset($permissions[$role][$feature]) ? $permissions[$role][$feature] : false;
+}
+
+// Function to get popular products
+function getPopularProducts($startDate, $endDate, $limit = 20, $sortBy = 'quantity', $isAdmin = false) {
+ global $conn;
+
+ // Add time components to make the date range inclusive
+ $startDateTime = $startDate . ' 00:00:00';
+ $endDateTime = $endDate . ' 23:59:59';
+
+ // Base query for popular products - modified to join jenis_barang for category
+ $baseQuery = "
+ SELECT
+ b.id_barang,
+ b.kode_barang,
+ b.nama_barang,
+ b.harga,
+ j.nama_jenis as kategori,
+ SUM(dt.jumlah) as total_quantity,
+ SUM(dt.total_harga) as total_revenue
+ FROM
+ barang b
+ JOIN
+ jenis_barang j ON b.id_jenis = j.id_jenis
+ JOIN
+ detail_transaksi dt ON b.id_barang = dt.id_barang
+ JOIN
+ transaksi t ON dt.id_transaksi = t.id_transaksi
+ WHERE
+ t.tanggal BETWEEN ? AND ?
+ GROUP BY
+ b.id_barang
+ ORDER BY
+ ";
+
+ // Add the appropriate ORDER BY clause
+ if ($sortBy == 'revenue') {
+ $baseQuery .= "total_revenue DESC";
+ } else {
+ $baseQuery .= "total_quantity DESC";
+ }
+
+ // Add limit
+ $baseQuery .= " LIMIT ?";
+
+ // Prepare and execute query
+ $stmt = mysqli_prepare($conn, $baseQuery);
+
+ if (!$stmt) {
+ return [
+ 'result' => false,
+ 'error' => 'Failed to prepare query: ' . mysqli_error($conn),
+ 'has_data' => false
+ ];
+ }
+
+ mysqli_stmt_bind_param($stmt, "ssi", $startDateTime, $endDateTime, $limit);
+ $execResult = mysqli_stmt_execute($stmt);
+
+ if (!$execResult) {
+ return [
+ 'result' => false,
+ 'error' => 'Failed to execute query: ' . mysqli_stmt_error($stmt),
+ 'has_data' => false
+ ];
+ }
+
+ $result = mysqli_stmt_get_result($stmt);
+ $rowCount = mysqli_num_rows($result);
+
+ // Get summary totals
+ $summaryQuery = "
+ SELECT
+ COUNT(DISTINCT dt.id_barang) as total_products,
+ SUM(dt.jumlah) as total_items_sold,
+ SUM(dt.total_harga) as total_revenue
+ FROM
+ detail_transaksi dt
+ JOIN
+ transaksi t ON dt.id_transaksi = t.id_transaksi
+ WHERE
+ t.tanggal BETWEEN ? AND ?
+ ";
+
+ $summaryStmt = mysqli_prepare($conn, $summaryQuery);
+
+ if (!$summaryStmt) {
+ return [
+ 'result' => $result,
+ 'summary' => null,
+ 'error' => 'Failed to prepare summary query: ' . mysqli_error($conn),
+ 'has_data' => ($rowCount > 0)
+ ];
+ }
+
+ mysqli_stmt_bind_param($summaryStmt, "ss", $startDateTime, $endDateTime);
+ $summaryExecResult = mysqli_stmt_execute($summaryStmt);
+
+ if (!$summaryExecResult) {
+ return [
+ 'result' => $result,
+ 'summary' => null,
+ 'error' => 'Failed to execute summary query: ' . mysqli_stmt_error($summaryStmt),
+ 'has_data' => ($rowCount > 0)
+ ];
+ }
+
+ $summaryResult = mysqli_stmt_get_result($summaryStmt);
+ $summary = mysqli_fetch_assoc($summaryResult);
+
+ // Handle case where no data is found
+ if (!$summary) {
+ $summary = [
+ 'total_products' => 0,
+ 'total_items_sold' => 0,
+ 'total_revenue' => 0
+ ];
+ }
+
+ // For non-admin users, potentially mask certain financial data
+ if (!$isAdmin) {
+ // Keep the summary accessible but hide financial details if needed
+ if (!canAccessFeature('view_financial')) {
+ $summary['total_revenue'] = 0;
+ }
+ }
+
+ // Get category breakdown for pie chart - using jenis_barang table
+ $categoryQuery = "
+ SELECT
+ j.nama_jenis as kategori,
+ SUM(dt.jumlah) as total_quantity,
+ SUM(dt.total_harga) as total_revenue,
+ COUNT(DISTINCT b.id_barang) as unique_products
+ FROM
+ barang b
+ JOIN
+ jenis_barang j ON b.id_jenis = j.id_jenis
+ JOIN
+ detail_transaksi dt ON b.id_barang = dt.id_barang
+ JOIN
+ transaksi t ON dt.id_transaksi = t.id_transaksi
+ WHERE
+ t.tanggal BETWEEN ? AND ?
+ GROUP BY
+ j.nama_jenis
+ ORDER BY
+ total_quantity DESC
+ ";
+
+ $categoryStmt = mysqli_prepare($conn, $categoryQuery);
+
+ if (!$categoryStmt) {
+ return [
+ 'result' => $result,
+ 'summary' => $summary,
+ 'categories' => null,
+ 'error' => 'Failed to prepare category query: ' . mysqli_error($conn),
+ 'has_data' => ($rowCount > 0)
+ ];
+ }
+
+ mysqli_stmt_bind_param($categoryStmt, "ss", $startDateTime, $endDateTime);
+ $categoryExecResult = mysqli_stmt_execute($categoryStmt);
+
+ if (!$categoryExecResult) {
+ return [
+ 'result' => $result,
+ 'summary' => $summary,
+ 'categories' => null,
+ 'error' => 'Failed to execute category query: ' . mysqli_stmt_error($categoryStmt),
+ 'has_data' => ($rowCount > 0)
+ ];
+ }
+
+ $categoryResult = mysqli_stmt_get_result($categoryStmt);
+
+ return [
+ 'result' => $result,
+ 'summary' => $summary,
+ 'categories' => $categoryResult,
+ 'has_data' => ($rowCount > 0)
+ ];
+}
+
+// Function to apply date ranges based on preset or manual selection
+function getStartDate($datePresets, $activePreset) {
+ // If preset is selected, use preset dates
+ if (!empty($activePreset) && isset($datePresets[$activePreset])) {
+ return $datePresets[$activePreset]['start'];
+ }
+
+ // Otherwise use the date parameters if provided
+ $startDate = isset($_GET['start_date']) && !empty($_GET['start_date'])
+ ? $_GET['start_date']
+ : date('Y-m-01'); // Default to first day of current month
+
+ // Make sure date is in YYYY-MM-DD format
+ if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $startDate)) {
+ $startDate = date('Y-m-01');
+ }
+
+ return $startDate;
+}
+
+function getEndDate($datePresets, $activePreset) {
+ // If preset is selected, use preset dates
+ if (!empty($activePreset) && isset($datePresets[$activePreset])) {
+ return $datePresets[$activePreset]['end'];
+ }
+
+ // Otherwise use the date parameters if provided
+ $endDate = isset($_GET['end_date']) && !empty($_GET['end_date'])
+ ? $_GET['end_date']
+ : date('Y-m-d'); // Default to today
+
+ // Make sure date is in YYYY-MM-DD format
+ if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $endDate)) {
+ $endDate = date('Y-m-d');
+ }
+
+ return $endDate;
+}
+
+// Handle PDF export functionality
+function exportToPDF($startDate, $endDate, $limit, $sortBy, $isAdmin) {
+ // Check permissions
+ if (!($isAdmin || canAccessFeature('export_pdf'))) {
+ die("Access denied. You don't have permission to export to PDF.");
+ }
+
+ // Get the data
+ $queryData = getPopularProducts($startDate, $endDate, $limit, $sortBy, $isAdmin);
+
+ // Here you would implement PDF generation with a library like FPDF or TCPDF
+ // For example:
+ /*
+ require('fpdf/fpdf.php');
+
+ $pdf = new FPDF();
+ $pdf->AddPage();
+ $pdf->SetFont('Arial', 'B', 16);
+ $pdf->Cell(40, 10, 'Ayula Store - Laporan Produk Terlaris');
+
+ // Add more PDF generation code here
+
+ $pdf->Output('D', 'produk_terlaris.pdf');
+ exit;
+ */
+
+ // For now, just return a message
+ echo "
PDF Export
";
+ echo "
This functionality would generate a PDF for the selected date range: $startDate to $endDate
";
+ echo "
Implementation with a PDF library is required to complete this feature.
";
+ exit;
+}
\ No newline at end of file
diff --git a/views/report/popular-products/popular-products.css b/views/report/popular-products/popular-products.css
new file mode 100644
index 0000000..8beeb7c
--- /dev/null
+++ b/views/report/popular-products/popular-products.css
@@ -0,0 +1,305 @@
+/* Custom styles for popular products report */
+.product-card {
+ transition: all 0.3s ease;
+ border-radius: 10px;
+ overflow: hidden;
+ margin-bottom: 20px;
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.05);
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+}
+
+.product-card:hover {
+ transform: translateY(-5px);
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
+}
+
+/* Move rank badge outside card body */
+.rank-badge {
+ position: absolute;
+ top: 10px; /* Slightly above card */
+ left: 15px;
+ width: 40px;
+ height: 40px;
+ background-color: #7367f0;
+ color: white;
+ border-radius: 50%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ font-weight: bold;
+ font-size: 18px;
+ z-index: 10;
+}
+
+/* Special style for top 3 rankings */
+.top-3 {
+ background-color: #ff9f43;
+ font-size: 20px;
+}
+
+/* Ensure card title and content don't overlap */
+.top-product .card-body {
+ padding-top: 60px; /* Push content below rank badge */
+ padding-left: 15px;
+ padding-right: 15px;
+ position: relative;
+}
+
+.top-product .card-title {
+ font-size: 16px;
+ margin: 0;
+ padding: 10px 0;
+ background-color: #f8f9fa;
+ border-bottom: 1px solid #e9ecef;
+ font-weight: 600;
+}
+
+/* Add spacing between card content */
+.product-stats {
+ margin-top: 20px; /* Ensures enough space between content and stats */
+}
+
+/* Ensure the progress bar has some margin */
+.product-progress {
+ height: 8px;
+ margin-top: 10px; /* Space out progress bar from content */
+}
+
+/* Top products grid styles */
+.top-products-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
+ gap: 20px;
+ margin-top: 15px;
+}
+
+/* Responsive adjustment */
+@media (max-width: 768px) {
+ .top-products-grid {
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+ }
+
+ .rank-badge {
+ width: 35px;
+ height: 35px;
+ font-size: 16px;
+ top: -10px;
+ }
+
+ .product-card .card-body {
+ padding-top: 70px;
+ }
+}
+
+/* Ensure cards are aligned properly on small screens */
+@media (max-width: 576px) {
+ .product-card .card {
+ min-height: 250px;
+ }
+}
+
+/* Progress bar styles */
+.product-progress {
+ height: 8px;
+ margin-top: 5px;
+}
+
+/* Top products grid styles */
+.top-products-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
+ gap: 20px;
+ margin-top: 15px;
+}
+
+/* Memastikan card memiliki tinggi minimum */
+.product-card .card {
+ min-height: 200px;
+ height: 100%;
+ width: 100%; /* Pastikan lebar penuh */
+ display: flex;
+ flex-direction: column;
+ margin-left: auto; /* Pindahkan kartu ke kanan dengan margin kiri otomatis */
+ margin-right: 0; /* Menjaga margin kanan tidak tumpang tindih */
+}
+
+/* Custom styles for view toggle buttons */
+.view-toggle .btn {
+ padding: 0.25rem 0.5rem;
+}
+
+.view-toggle .btn i {
+ margin-right: 0;
+}
+
+/* Chart container styles */
+.chart-container {
+ position: relative;
+ height: 300px;
+ width: 100%;
+}
+
+/* Employee-specific warning styles */
+.employee-warning {
+ display: none;
+}
+
+.employee .employee-warning {
+ display: block;
+ padding: 5px 10px;
+ border-radius: 4px;
+ background-color: #fff8e1;
+ border-left: 4px solid #ffc107;
+ margin-bottom: 15px;
+}
+
+/* Role-based styling */
+.admin-only {
+ display: none;
+}
+
+.admin .admin-only {
+ display: block;
+}
+
+.employee .sensitive-financial {
+ filter: blur(4px);
+ position: relative;
+}
+
+.employee .sensitive-financial:hover::after {
+ content: "Data terbatas";
+ position: absolute;
+ left: 0;
+ top: 0;
+ background: rgba(255,0,0,0.2);
+ padding: 2px 5px;
+ border-radius: 3px;
+ font-size: 10px;
+ white-space: nowrap;
+}
+
+/* Disabled buttons for employees */
+.employee .disabled-for-employee {
+ opacity: 0.6;
+ pointer-events: none;
+}
+
+.employee .request-only {
+ display: inline-block;
+}
+
+.admin .request-only {
+ display: none;
+}
+
+/* Styling untuk tabel view */
+#table-view th:first-child {
+ width: 70px; /* Lebar tetap untuk kolom ranking */
+ text-align: center;
+}
+
+#table-view .badge {
+ font-size: 14px;
+ padding: 5px 8px;
+}
+
+/* Print specific styles */
+@media print {
+ .sidebar, .header, .no-print, #filter_inputs, .wordset, .search-set {
+ display: none !important;
+ }
+
+ .page-wrapper {
+ margin-left: 0 !important;
+ padding: 0 !important;
+ }
+
+ .card {
+ box-shadow: none !important;
+ margin-bottom: 1rem !important;
+ }
+
+ .content {
+ padding: 0 !important;
+ }
+
+ .table {
+ width: 100% !important;
+ }
+
+ .table th, .table td {
+ padding: 0.25rem !important;
+ }
+
+ .report-header {
+ display: block !important;
+ text-align: center;
+ margin-bottom: 20px;
+ }
+
+ .report-header h2 {
+ margin-bottom: 5px;
+ }
+
+ /* Force table view in print mode */
+ #table-view {
+ display: block !important;
+ }
+
+ #grid-view {
+ display: none !important;
+ }
+
+ /* Hide chart canvases as they don't print well */
+ canvas {
+ display: none !important;
+ }
+}
+
+/* Responsive adjustments */
+@media (max-width: 768px) {
+ .top-products-grid {
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+ }
+
+ .dash-widget {
+ margin-bottom: 15px;
+ }
+
+ .chart-container {
+ height: 250px;
+ }
+
+ .rank-badge {
+ width: 30px;
+ height: 30px;
+ font-size: 14px;
+ }
+
+ .rank-badge.top-3 {
+ width: 35px;
+ height: 35px;
+ font-size: 16px;
+ }
+
+ .top-product .card-title {
+ font-size: 14px;
+ min-height: 40px;
+ }
+}
+
+/* Make the table more compact on smaller screens */
+@media (max-width: 576px) {
+ .datanew th, .datanew td {
+ padding: 0.5rem 0.25rem;
+ font-size: 0.875rem;
+ }
+
+ .view-toggle {
+ margin-left: auto;
+ }
+}
diff --git a/views/report/popular-products/popular-products.js b/views/report/popular-products/popular-products.js
new file mode 100644
index 0000000..830dedc
--- /dev/null
+++ b/views/report/popular-products/popular-products.js
@@ -0,0 +1,460 @@
+/**
+ * popular-products.js - Script for handling product popularity report functionality
+ *
+ * This script manages:
+ * - DataTable initialization
+ * - Date filtering and form handling
+ * - View toggling (grid vs table)
+ * - Export functionality (PDF, Excel)
+ * - Chart initialization
+ */
+
+// Document ready function
+$(document).ready(function () {
+ // Initialize datatable with error handling
+ initializeDataTable();
+
+ // Handle date preset selection
+ handleDatePresets();
+
+ // Handle limit and sort changes
+ handleFilterChanges();
+
+ // Form validation
+ setupFormValidation();
+
+ // View toggle
+ setupViewToggle();
+
+ // Export functionality
+ setupExportFunctionality();
+
+ // Initialize Chart.js charts if they exist
+ if (typeof initializeCharts === 'function') {
+ initializeCharts();
+ }
+
+ // Hide loading overlay when page is fully loaded
+ hideLoadingWithDelay(500);
+
+ // Check user role for hiding UI elements
+ checkUserPermissions();
+});
+
+/**
+ * Check user permissions and adjust UI accordingly
+ */
+function checkUserPermissions() {
+ // Check if body has class 'employee' or 'user'
+ if ($('body').hasClass('employee') || $('body').hasClass('user')) {
+ // Hide any admin-only elements that might not have been hidden by PHP
+ $('.admin-only').hide();
+ }
+}
+
+/**
+ * Initialize the DataTable with appropriate settings
+ */
+function initializeDataTable() {
+ try {
+ $('.datanew').DataTable({
+ responsive: true,
+ language: {
+ search: '
Cari: _INPUT_',
+ searchPlaceholder: 'Cari produk...',
+ lengthMenu: '
Tampilkan: _MENU_',
+ paginate: {
+ 'first': 'Pertama',
+ 'last': 'Terakhir',
+ 'next': 'Berikutnya',
+ 'previous': 'Sebelumnya'
+ },
+ info: "Menampilkan _START_ sampai _END_ dari _TOTAL_ entri",
+ infoEmpty: "Menampilkan 0 sampai 0 dari 0 entri",
+ infoFiltered: "(disaring dari _MAX_ entri total)",
+ emptyTable: "Tidak ada data tersedia untuk periode yang dipilih",
+ zeroRecords: "Tidak ditemukan catatan yang cocok"
+ },
+ dom: '<"top"fl>rt<"bottom"ip><"clear">',
+ lengthMenu: [10, 25, 50, 100],
+ pageLength: 10,
+ drawCallback: function() {
+ hideLoadingOverlay();
+ }
+ });
+ } catch (e) {
+ console.error("DataTable initialization error:", e);
+ hideLoadingOverlay();
+ }
+}
+
+/**
+ * Handle date preset selection changes
+ */
+function handleDatePresets() {
+ $('#preset').on('change', function() {
+ if ($(this).val() === '') {
+ // Custom date range selected - show custom date inputs
+ $('#custom-date-inputs').show();
+ } else {
+ // Preset selected - hide custom date inputs and show loading overlay
+ $('#custom-date-inputs').hide();
+ showLoadingOverlay();
+
+ // Add a small delay before submitting to ensure loading overlay is visible
+ setTimeout(function() {
+ $('#date-filter-form').submit();
+ }, 100);
+ }
+ });
+}
+
+/**
+ * Handle limit and sort_by changes (auto-submit form)
+ */
+function handleFilterChanges() {
+ $('#limit, #sort_by').on('change', function() {
+ showLoadingOverlay();
+
+ // Add a small delay before submitting
+ setTimeout(function() {
+ $('#date-filter-form').submit();
+ }, 100);
+ });
+
+ // Handle reset button - show loading overlay
+ $('a[href="?reset=1"]').on('click', function() {
+ showLoadingOverlay();
+ });
+}
+
+/**
+ * Setup form validation
+ */
+function setupFormValidation() {
+ $('#date-filter-form').on('submit', function(e) {
+ showLoadingOverlay();
+
+ // Only validate if custom date range is selected
+ if ($('#preset').val() === '') {
+ var startDate = $('#start_date').val();
+ var endDate = $('#end_date').val();
+
+ if (!startDate || !endDate) {
+ alert('Harap pilih tanggal awal dan akhir');
+ hideLoadingOverlay();
+ e.preventDefault();
+ return false;
+ }
+
+ if (new Date(startDate) > new Date(endDate)) {
+ alert('Tanggal awal tidak boleh lebih besar dari tanggal akhir');
+ hideLoadingOverlay();
+ e.preventDefault();
+ return false;
+ }
+ }
+
+ return true;
+ });
+}
+
+/**
+ * Setup view toggle between grid and table
+ */
+function setupViewToggle() {
+ $('.view-toggle button').on('click', function() {
+ const viewType = $(this).data('view');
+
+ // Update active button
+ $('.view-toggle button').removeClass('active');
+ $(this).addClass('active');
+
+ // Show/hide appropriate view
+ if (viewType === 'grid') {
+ $('#grid-view').show();
+ $('#table-view').hide();
+ } else {
+ $('#grid-view').hide();
+ $('#table-view').show();
+ }
+ });
+}
+
+/**
+ * Setup export functionality
+ */
+function setupExportFunctionality() {
+ // PDF Export
+ $('.pdf-export').on('click', function(e) {
+ e.preventDefault();
+
+ showLoadingOverlay();
+
+ // Check if the user has permission (controlled by CSS class)
+ if ($(this).hasClass('disabled-for-employee')) {
+ showPermissionModal('Ekspor PDF', 'Laporan ini tidak dapat diekspor ke PDF oleh akun karyawan.');
+ hideLoadingOverlay();
+ return;
+ }
+
+ // If we have permission, proceed with export
+ exportToPDF();
+ });
+
+ // Excel Export
+ $('.excel-export').on('click', function(e) {
+ e.preventDefault();
+
+ showLoadingOverlay();
+
+ // Check if the user has permission
+ if ($(this).hasClass('disabled-for-employee')) {
+ showPermissionModal('Ekspor Excel', 'Laporan ini tidak dapat diekspor ke Excel oleh akun karyawan.');
+ hideLoadingOverlay();
+ return;
+ }
+
+ // If we have permission, proceed with export
+ exportToExcel();
+ });
+
+ // Print Report
+ $('.print-report').on('click', function(e) {
+ e.preventDefault();
+
+ // Check if the user has permission
+ if ($(this).hasClass('disabled-for-employee')) {
+ showPermissionModal('Cetak Laporan', 'Pencetakan laporan dibatasi hanya untuk akun administrator.');
+ return;
+ }
+
+ // If we have permission, proceed with print
+ printReport();
+ });
+
+ // For employee access - handle restricted action attempts
+ $('.employee-print, .employee-export').on('click', function(e) {
+ e.preventDefault();
+
+ // Get the action type from data attribute or default
+ var actionType = $(this).data('action') === 'print_attempt' ? 'Cetak Laporan' : 'Ekspor Laporan';
+ var message = $(this).data('action') === 'print_attempt'
+ ? 'Pencetakan laporan dibatasi hanya untuk akun administrator.'
+ : 'Ekspor data dibatasi hanya untuk akun administrator.';
+
+ showPermissionModal(actionType, message);
+ });
+}
+
+/**
+ * Show permission denied modal
+ */
+function showPermissionModal(actionType, message) {
+ // Set the action details
+ $('#actionDetails').html(
+ '
Tindakan yang Dicoba: ' + actionType + '
' +
+ '
' + message + ''
+ );
+
+ // Show the modal
+ var permissionModal = new bootstrap.Modal(document.getElementById('permissionModal'));
+ permissionModal.show();
+}
+
+/**
+ * Print the current report
+ */
+function printReport() {
+ // Add print-specific styling
+ $('
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Rentang Tanggal Saat Ini: sampai
+
+ 'Hari Ini',
+ 'yesterday' => 'Kemarin',
+ 'this_week' => 'Minggu Ini',
+ 'last_week' => 'Minggu Lalu',
+ 'this_month' => 'Bulan Ini',
+ 'last_30_days' => '30 Hari Terakhir',
+ 'last_month' => 'Bulan Lalu',
+ 'last_90_days' => '90 Hari Terakhir',
+ 'last_quarter' => 'Kuartal Terakhir',
+ 'last_year' => 'Tahun Lalu',
+ 'all_time' => 'Sepanjang Waktu'
+ ];
+ $label = isset($presetLabels[$activePreset]) ? $presetLabels[$activePreset] : $datePresets[$activePreset]['label'];
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Tidak ada transaksi ditemukan untuk periode yang dipilih
+
+
+ Tidak ada transaksi penjualan yang tercatat untuk tahun lalu ().
+
+ Coba pilih rentang tanggal atau preset tanggal yang berbeda.
+
+
+
+ Reset Filter
+
+
+
+
+
+
+
+
+ ID Transaksi |
+ Tanggal |
+ Item |
+ Produk |
+
+ Total |
+
+
+ Tunai |
+ Kembalian |
+
+ Status |
+
+ Aksi |
+
+
+
+ 0): ?>
+
+
+
+ |
+ |
+ |
+ |
+
+
+
+ Rp. |
+
+
+
+ Rp. |
+ Rp. |
+
+
+ Selesai |
+
+
+
+
+ Lihat
+
+
+
+ Cetak
+
+
+ |
+
+
+
+
+ Tidak ada transaksi ditemukan untuk periode yang dipilih |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Total Transaksi:
+
+
+
+ Total Item Terjual:
+
+
+
+
+
+ Total:
+ Rp.
+
+
+ Rata-rata Harian:
+ Rp. 0 ? $totals['grand_total'] / $daysDiff : 0;
+ echo number_format($dailyAvg);
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Untuk laporan keuangan yang lebih rinci atau untuk mengekspor data, silakan hubungi administrator Anda.
+
+
+
+
+
+
+ Transaksi Dilihat:
+
+
+
+ Item Diproses:
+
+
+
+
+
+ Laporan Dibuat:
+
+
+
+ Level Akses:
+ Karyawan
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Fitur Dibatasi
+
+
Fitur ini hanya tersedia untuk administrator dan personel yang berwenang.
+
Akun karyawan tidak memiliki akses ke fungsi cetak atau ekspor.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+