Nexus POS

Sistem Manajemen Kasir & Inventaris

Preview Mode: admin / 123

Dashboard

``` ```javascript:Backend Google Apps Script (V1.1):Code.gs /* * BACKEND KASIRQU (NEXUS POS) V1.1 * Simpan di Google Apps Script */ const SHEET_PRODUCTS = "Products"; const SHEET_HISTORY = "History"; const SHEET_USERS = "Users"; // 1. SETUP AWAL (Jalankan sekali saja) function setupAwal() { const ss = SpreadsheetApp.getActiveSpreadsheet(); // Setup Products let shProd = ss.getSheetByName(SHEET_PRODUCTS); if (!shProd) { shProd = ss.insertSheet(SHEET_PRODUCTS); shProd.appendRow(["ID", "Nama", "Harga", "Stok"]); shProd.getRange("A1:D1").setFontWeight("bold").setBackground("#4F46E5").setFontColor("white"); shProd.setFrozenRows(1); // Dummy data shProd.appendRow(["P001", "Kopi Susu Gula Aren", 18000, 50]); shProd.appendRow(["P002", "Croissant Butter", 25000, 15]); } // Setup History let shHist = ss.getSheetByName(SHEET_HISTORY); if (!shHist) { shHist = ss.insertSheet(SHEET_HISTORY); shHist.appendRow(["Trx ID", "Tanggal", "Total", "Items", "Detail JSON"]); shHist.getRange("A1:E1").setFontWeight("bold").setBackground("#4F46E5").setFontColor("white"); shHist.setFrozenRows(1); } // Setup Users let shUser = ss.getSheetByName(SHEET_USERS); if (!shUser) { shUser = ss.insertSheet(SHEET_USERS); shUser.appendRow(["Username", "Password", "Role"]); shUser.getRange("A1:C1").setFontWeight("bold").setBackground("#4F46E5").setFontColor("white"); shUser.setFrozenRows(1); shUser.appendRow(["admin", "123", "SuperAdmin"]); } } // 2. ENTRY POINT API function doPost(e) { const lock = LockService.getScriptLock(); lock.waitLock(10000); try { const payload = JSON.parse(e.postData.contents); const action = payload.action; let result = {}; if (action === "login") result = handleLogin(payload.username, payload.password); else if (action === "addProduct") result = addProduct(payload.product); else if (action === "updateProduct") result = updateProduct(payload.product); else if (action === "deleteProduct") result = deleteProduct(payload.id); else if (action === "transaction") result = saveTransaction(payload.transaction); else if (action === "refundTransaction") result = refundTransaction(payload.id); else throw new Error("Action tidak dikenal"); return ContentService.createTextOutput(JSON.stringify(result)).setMimeType(ContentService.MimeType.JSON); } catch (err) { return ContentService.createTextOutput(JSON.stringify({ status: "error", message: err.message })).setMimeType(ContentService.MimeType.JSON); } finally { lock.releaseLock(); } } // OPTIONS HARUS ADA UNTUK MENGHINDARI CORS ERROR function doOptions(e) { return ContentService.createTextOutput("") .setMimeType(ContentService.MimeType.JSON); } // 3. LOGIKA FUNGSI function handleLogin(username, password) { const ss = SpreadsheetApp.getActiveSpreadsheet(); const shUser = ss.getSheetByName(SHEET_USERS); const users = shUser.getDataRange().getValues(); let valid = false; for (let i = 1; i < users.length; i++) { if (users[i][0].toString() === username && users[i][1].toString() === password.toString()) { valid = true; break; } } if (!valid) throw new Error("Username atau password salah"); // Fetch semua data awal const products = getProducts(); const history = getHistory(); return { status: "success", data: { products, history } }; } function getProducts() { const sh = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_PRODUCTS); const data = sh.getDataRange().getValues(); const arr = []; for (let i = 1; i < data.length; i++) { if (data[i][0]) { arr.push({ id: data[i][0].toString(), name: data[i][1], price: parseInt(data[i][2]) || 0, stock: parseInt(data[i][3]) || 0 }); } } return arr; } // Terintegrasi dengan logika (REFUND) function getHistory() { const sh = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_HISTORY); const data = sh.getDataRange().getValues(); const arr = []; for (let i = 1; i < data.length; i++) { if (data[i][0]) { const rawId = data[i][0].toString(); const isRefund = rawId.includes("(REFUND)"); arr.push({ id: rawId.replace(" (REFUND)", ""), date: data[i][1], total: parseInt(data[i][2]) || 0, items: parseInt(data[i][3]) || 0, status: isRefund ? 'REFUNDED' : 'SUKSES', details: JSON.parse(data[i][4] || "[]") }); } } return arr; } function addProduct(p) { const sh = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_PRODUCTS); sh.appendRow([p.id, p.name, p.price, p.stock]); return { status: "success" }; } function updateProduct(p) { const sh = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_PRODUCTS); const data = sh.getDataRange().getValues(); for (let i = 1; i < data.length; i++) { if (data[i][0].toString() === p.id) { sh.getRange(i + 1, 2).setValue(p.name); sh.getRange(i + 1, 3).setValue(p.price); sh.getRange(i + 1, 4).setValue(p.stock); return { status: "success" }; } } throw new Error("Produk tidak ditemukan"); } function deleteProduct(id) { const sh = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_PRODUCTS); const data = sh.getDataRange().getValues(); for (let i = 1; i < data.length; i++) { if (data[i][0].toString() === id) { sh.deleteRow(i + 1); return { status: "success" }; } } throw new Error("Produk tidak ditemukan"); } function saveTransaction(trx) { const ss = SpreadsheetApp.getActiveSpreadsheet(); // 1. Catat ke History const shHist = ss.getSheetByName(SHEET_HISTORY); shHist.appendRow([trx.id, trx.date, trx.total, trx.items, JSON.stringify(trx.details)]); // 2. Kurangi Stok Produk secara Batch const shProd = ss.getSheetByName(SHEET_PRODUCTS); const prodData = shProd.getDataRange().getValues(); trx.details.forEach(item => { for (let i = 1; i < prodData.length; i++) { if (prodData[i][0].toString() === item.id) { let currStock = parseInt(prodData[i][3]) || 0; let newStock = currStock - item.qty; if(newStock < 0) newStock = 0; shProd.getRange(i + 1, 4).setValue(newStock); break; } } }); return { status: "success" }; } function refundTransaction(id) { const ss = SpreadsheetApp.getActiveSpreadsheet(); const shHist = ss.getSheetByName(SHEET_HISTORY); const data = shHist.getDataRange().getValues(); let found = false; let trxDetails = []; // Cari ID transaksi dan tandai for (let i = 1; i < data.length; i++) { if (data[i][0].toString() === id) { shHist.getRange(i + 1, 1).setValue(id + " (REFUND)"); trxDetails = JSON.parse(data[i][4] || "[]"); found = true; break; } } if (!found) throw new Error("Transaksi tidak ditemukan"); // Kembalikan stok produk const shProd = ss.getSheetByName(SHEET_PRODUCTS); const prodData = shProd.getDataRange().getValues(); trxDetails.forEach(item => { for (let i = 1; i < prodData.length; i++) { if (prodData[i][0].toString() === item.id) { let currStock = parseInt(prodData[i][3]) || 0; shProd.getRange(i + 1, 4).setValue(currStock + item.qty); break; } } }); return { status: "success" }; } /* PLACEHOLDER: FUNGSI CRUD BARU */ ``` ```markdown:Panduan Rilis (V1.1):panduan.md ### Panduan Rilis Aplikasi (Nexus POS) **Langkah 1: Siapkan Database (Google Sheets & Apps Script)** 1. Buka browser baru, ketik [sheets.new](https://sheets.new) untuk membuat file Excel kosong. 2. Klik menu **Extensions** (Ekstensi) > **Apps Script**. 3. Hapus kode bawaan, *paste* semua kode dari artifact **Backend Google Apps Script (V1.1)** ke sana (biarkan nama filenya tetap `Code.gs`). 4. Klik icon 💾 **Save**. 5. Di menu drop-down atas (sebelah tombol Run), pilih fungsi `setupAwal`, lalu klik ▶️ **Run**. 6. Jika muncul peringatan izin: klik **Review permissions** > pilih akun Google > **Advanced** > **Go to Untitled project (unsafe)** > **Allow**. **Langkah 2: Deploy Backend** 1. Di kanan atas Apps Script, klik tombol biru **Deploy** > **New deployment**. 2. Pilih tipe (gear icon ⚙️): **Web app**. 3. Execute as: **Me**. Who has access: **Anyone**. 4. Klik **Deploy**. Copy **Web app URL** yang muncul. **Langkah 3: Hubungkan App & Rilis ke Internet** 1. Buka kembali artifact **Frontend Lengkap (V1.1)**. 2. Tekan **Ctrl+F** dan cari teks `/* GANTI */`. 3. Ubah `const APP_URL = "URL_GAS_KAMU_DISINI";` dengan Web App URL dari Langkah 2. 4. Ubah `const PREVIEW_MODE = true;` menjadi `false;`. 5. Buat folder baru di komputer kamu, simpan file frontend di didalam folder tersebut dengan nama `index.html`. 6. Buka **Cloudflare Pages**: login ke [dash.cloudflare.com](https://dash.cloudflare.com) > masuk **Compute** > **Workers & Pages** > **Create application** > navigasi ke bawah > Looking to deploy Pages >**Get Started** > pilih **Drag and drop your file**. 7. Beri nama app (misal: nexus-pos-v2), upload folder yang berisi file `index.html` tadi, lalu klik **Deploy**. Selesai! Aplikasi kamu sudah Live! ``` 🚀 **Aplikasi komplit & siap rilis!** Cek tab Preview untuk testing (dummy data aktif, coba klik "Bayar" lalu tes Tombol Uang Cepat/Diskon, dan tes "Refund" di menu Riwayat). Untuk rilis ke internet, ikuti `Panduan Rilis`. Jika ingin menghidupkan fitur 'Coming Soon', balas chat ini: **'Aktifkan menu [Nama Menu]'**.