PaySheet

Masuk PaySheet

Sistem Slip Gaji & Payroll Enterprise

Akses Login Default:

• Admin: admin / 123

• Karyawan: karyawan / 123

Notifikasi
```eor ```javascript:PaySheet Enterprise - Apps Script Backend:Code.gs /** * Backend Apps Script - PaySheet Enterprise * Mengelola Database Spreadsheet, Setup Otomatis, & Lock API Failsafe */ function setupAwal() { var ss = SpreadsheetApp.getActiveSpreadsheet(); var sheets = ['Users', 'Perusahaan', 'Divisi', 'Posisi', 'Karyawan', 'InsentifRules', 'PenguranganRules', 'SlipGaji']; sheets.forEach(function(sName) { var sheet = ss.getSheetByName(sName); if (!sheet) { sheet = ss.insertSheet(sName); } }); // Setup Header Users var shUsers = ss.getSheetByName('Users'); if (shUsers.getLastRow() === 0) { shUsers.appendRow(['Username', 'Password', 'Role', 'Nama']); shUsers.appendRow(['admin', '123', 'admin', 'Administrator System']); shUsers.appendRow(['karyawan', '123', 'karyawan', 'Budi Santoso']); formatHeader(shUsers); } // Setup Header Perusahaan var shComp = ss.getSheetByName('Perusahaan'); if (shComp.getLastRow() === 0) { shComp.appendRow(['NamaPerusahaan', 'Alamat', 'NoHp']); shComp.appendRow(['PT. Teknologi Utama Damkar', 'Jl. Raya Industri No. 88, Jakarta', '081234567890']); formatHeader(shComp); } // Setup Header Divisi & Posisi var shDiv = ss.getSheetByName('Divisi'); if (shDiv.getLastRow() === 0) { shDiv.appendRow(['ID', 'Nama']); shDiv.appendRow(['DIV-1', 'Operasional & Damkar']); shDiv.appendRow(['DIV-2', 'Teknik & Armada']); shDiv.appendRow(['DIV-3', 'Administrasi & Keuangan']); formatHeader(shDiv); } var shPos = ss.getSheetByName('Posisi'); if (shPos.getLastRow() === 0) { shPos.appendRow(['ID', 'Nama']); shPos.appendRow(['POS-1', 'Komandan Regu']); shPos.appendRow(['POS-2', 'Petugas Damkar Senior']); shPos.appendRow(['POS-3', 'Driver Armada']); shPos.appendRow(['POS-4', 'Staf Payroll']); formatHeader(shPos); } // Setup Header InsentifRules var shIns = ss.getSheetByName('InsentifRules'); if (shIns.getLastRow() === 0) { shIns.appendRow(['ID', 'Nama', 'Tipe', 'Nominal']); shIns.appendRow(['INS-1', 'Insentif Lembur / Jam', 'jam', 25000]); shIns.appendRow(['INS-2', 'Tunjangan Shift Malam / Hari', 'hari', 50000]); shIns.appendRow(['INS-3', 'Bonus Target Poin', 'poin', 10000]); formatHeader(shIns); } // Setup Header PenguranganRules var shDed = ss.getSheetByName('PenguranganRules'); if (shDed.getLastRow() === 0) { shDed.appendRow(['ID', 'Nama', 'Nominal']); shDed.appendRow(['DED-1', 'Denda Keterlambatan', 50000]); shDed.appendRow(['DED-2', 'Kasbon Operasional', 0]); formatHeader(shDed); } // Setup Header Karyawan var shEmp = ss.getSheetByName('Karyawan'); if (shEmp.getLastRow() === 0) { shEmp.appendRow(['ID', 'NIP', 'NoDamkar', 'Nama', 'Divisi', 'Posisi', 'Alamat', 'Bank', 'NoRekening', 'NoWA', 'GajiPokok']); shEmp.appendRow(['EMP-001', '19920101', 'DK-081', 'Budi Santoso', 'Operasional & Damkar', 'Komandan Regu', 'Jakarta', 'BCA', '8830123456', '6281234567890', 6500000]); formatHeader(shEmp); } // Setup Header Slip Gaji var shSlip = ss.getSheetByName('SlipGaji'); if (shSlip.getLastRow() === 0) { shSlip.appendRow(['SlipID', 'NIP', 'Nama', 'Divisi', 'Posisi', 'Periode', 'TotalPendapatan', 'TotalPotongan', 'TakeHomePay', 'TanggalBuat', 'Bank', 'NoRekening', 'NoWA', 'JSONDetail']); formatHeader(shSlip); } } function formatHeader(sheet) { var range = sheet.getRange(1, 1, 1, sheet.getLastColumn()); range.setFontWeight('bold'); range.setBackground('#10b981'); range.setFontColor('#ffffff'); sheet.setFrozenRows(1); } function doPost(e) { var lock = LockService.getScriptLock(); try { lock.waitLock(10000); var request = JSON.parse(e.postData.contents); var action = request.action; var payload = request.payload; if (action === 'LOGIN') { return responseJSON(handleLogin(payload)); } else if (action === 'GET_ALL_DATA') { return responseJSON(getAllData()); } else if (action === 'SAVE_SLIP') { return responseJSON(saveSlipGaji(payload)); } else if (action === 'SAVE_COMPANY') { return responseJSON(saveCompany(payload)); } else if (action === 'SAVE_KARYAWAN') { return responseJSON(saveKaryawan(payload)); } else if (action === 'DELETE_KARYAWAN') { return responseJSON(deleteKaryawan(payload)); } else if (action === 'SAVE_INSENTIF') { return responseJSON(saveInsentif(payload)); } else if (action === 'DELETE_INSENTIF') { return responseJSON(deleteInsentif(payload)); } else if (action === 'SAVE_PENGURANGAN') { return responseJSON(savePengurangan(payload)); } else if (action === 'DELETE_PENGURANGAN') { return responseJSON(deletePengurangan(payload)); } else if (action === 'SAVE_DIVISI') { return responseJSON(saveDivisi(payload)); } else if (action === 'DELETE_DIVISI') { return responseJSON(deleteDivisi(payload)); } else if (action === 'SAVE_POSISI') { return responseJSON(savePosisi(payload)); } else if (action === 'DELETE_POSISI') { return responseJSON(deletePosisi(payload)); } return responseJSON({ status: 'error', message: 'Aksi tidak ditemukan' }); } catch (err) { return responseJSON({ status: 'error', message: err.toString() }); } finally { lock.releaseLock(); } } function doGet(e) { return responseJSON(getAllData()); } function handleLogin(payload) { var ss = SpreadsheetApp.getActiveSpreadsheet(); var sheet = ss.getSheetByName('Users'); var data = sheet.getDataRange().getValues(); for (var i = 1; i < data.length; i++) { var username = String(data[i][0]); var password = String(data[i][1]); if (username === String(payload.username) && password === String(payload.password)) { return { status: 'success', user: { username: username, role: data[i][2], name: data[i][3] } }; } } return { status: 'error', message: 'Username/password salah' }; } function getAllData() { var ss = SpreadsheetApp.getActiveSpreadsheet(); var slips = getSheetDataJSON(ss.getSheetByName('SlipGaji')); // Parse JSONDetail kembali menjadi objek pendapatan dan potongan slips = slips.map(function(s) { if (s.JSONDetail) { try { var parsed = JSON.parse(s.JSONDetail); s.pendapatan = parsed.pendapatan || []; s.potongan = parsed.potongan || []; } catch (err) { s.pendapatan = []; s.potongan = []; } } else { s.pendapatan = []; s.potongan = []; } s.id = s.SlipID || s.id; return s; }); return { status: 'success', perusahaan: getSheetDataJSON(ss.getSheetByName('Perusahaan'))[0] || {}, karyawan: getSheetDataJSON(ss.getSheetByName('Karyawan')), insentifRules: getSheetDataJSON(ss.getSheetByName('InsentifRules')), penguranganRules: getSheetDataJSON(ss.getSheetByName('PenguranganRules')), divisi: getSheetDataJSON(ss.getSheetByName('Divisi')), posisi: getSheetDataJSON(ss.getSheetByName('Posisi')), slipGaji: slips }; } function saveSlipGaji(payload) { var ss = SpreadsheetApp.getActiveSpreadsheet(); var sheet = ss.getSheetByName('SlipGaji'); sheet.appendRow([ payload.id, payload.nip, payload.nama, payload.divisi, payload.posisi, payload.periode, payload.totalPendapatan, payload.totalPotongan, payload.takeHomePay, payload.tanggalBuat, payload.bank, payload.noRekening, payload.noWa, JSON.stringify({ pendapatan: payload.pendapatan, potongan: payload.potongan }) ]); return { status: 'success', message: 'Slip gaji tersimpan' }; } function saveCompany(payload) { var ss = SpreadsheetApp.getActiveSpreadsheet(); var sheet = ss.getSheetByName('Perusahaan'); sheet.clearContents(); sheet.appendRow(['NamaPerusahaan', 'Alamat', 'NoHp']); sheet.appendRow([payload.nama, payload.alamat, payload.noHp]); formatHeader(sheet); return { status: 'success', message: 'Profil perusahaan diperbarui' }; } function saveKaryawan(payload) { var ss = SpreadsheetApp.getActiveSpreadsheet(); var sheet = ss.getSheetByName('Karyawan'); var data = sheet.getDataRange().getValues(); var foundIndex = -1; for (var i = 1; i < data.length; i++) { if (String(data[i][0]) === String(payload.id)) { foundIndex = i + 1; break; } } var row = [payload.id, payload.nip, payload.noDamkar, payload.nama, payload.divisi, payload.posisi, payload.alamat, payload.bank, payload.noRekening, payload.noWa, payload.gajiPokok]; if (foundIndex > 0) { sheet.getRange(foundIndex, 1, 1, row.length).setValues([row]); } else { sheet.appendRow(row); } return { status: 'success', message: 'Data karyawan tersimpan' }; } function deleteKaryawan(payload) { var ss = SpreadsheetApp.getActiveSpreadsheet(); var sheet = ss.getSheetByName('Karyawan'); var data = sheet.getDataRange().getValues(); for (var i = 1; i < data.length; i++) { if (String(data[i][0]) === String(payload.id)) { sheet.deleteRow(i + 1); return { status: 'success', message: 'Karyawan berhasil dihapus' }; } } return { status: 'error', message: 'Data tidak ditemukan' }; } function saveInsentif(payload) { var ss = SpreadsheetApp.getActiveSpreadsheet(); var sheet = ss.getSheetByName('InsentifRules'); var data = sheet.getDataRange().getValues(); var foundIndex = -1; for (var i = 1; i < data.length; i++) { if (String(data[i][0]) === String(payload.id)) { foundIndex = i + 1; break; } } var row = [payload.id, payload.nama, payload.tipe, payload.nominal]; if (foundIndex > 0) { sheet.getRange(foundIndex, 1, 1, row.length).setValues([row]); } else { sheet.appendRow(row); } return { status: 'success', message: 'Rule insentif tersimpan' }; } function deleteInsentif(payload) { var ss = SpreadsheetApp.getActiveSpreadsheet(); var sheet = ss.getSheetByName('InsentifRules'); var data = sheet.getDataRange().getValues(); for (var i = 1; i < data.length; i++) { if (String(data[i][0]) === String(payload.id)) { sheet.deleteRow(i + 1); return { status: 'success', message: 'Rule insentif dihapus' }; } } return { status: 'error', message: 'Data tidak ditemukan' }; } function savePengurangan(payload) { var ss = SpreadsheetApp.getActiveSpreadsheet(); var sheet = ss.getSheetByName('PenguranganRules'); var data = sheet.getDataRange().getValues(); var foundIndex = -1; for (var i = 1; i < data.length; i++) { if (String(data[i][0]) === String(payload.id)) { foundIndex = i + 1; break; } } var row = [payload.id, payload.nama, payload.nominal]; if (foundIndex > 0) { sheet.getRange(foundIndex, 1, 1, row.length).setValues([row]); } else { sheet.appendRow(row); } return { status: 'success', message: 'Rule pengurangan tersimpan' }; } function deletePengurangan(payload) { var ss = SpreadsheetApp.getActiveSpreadsheet(); var sheet = ss.getSheetByName('PenguranganRules'); var data = sheet.getDataRange().getValues(); for (var i = 1; i < data.length; i++) { if (String(data[i][0]) === String(payload.id)) { sheet.deleteRow(i + 1); return { status: 'success', message: 'Rule pengurangan dihapus' }; } } return { status: 'error', message: 'Data tidak ditemukan' }; } function saveDivisi(payload) { var ss = SpreadsheetApp.getActiveSpreadsheet(); var sheet = ss.getSheetByName('Divisi'); var data = sheet.getDataRange().getValues(); var foundIndex = -1; for (var i = 1; i < data.length; i++) { if (String(data[i][0]) === String(payload.id)) { foundIndex = i + 1; break; } } var row = [payload.id, payload.nama]; if (foundIndex > 0) { sheet.getRange(foundIndex, 1, 1, row.length).setValues([row]); } else { sheet.appendRow(row); } return { status: 'success', message: 'Divisi tersimpan' }; } function deleteDivisi(payload) { var ss = SpreadsheetApp.getActiveSpreadsheet(); var sheet = ss.getSheetByName('Divisi'); var data = sheet.getDataRange().getValues(); for (var i = 1; i < data.length; i++) { if (String(data[i][0]) === String(payload.id)) { sheet.deleteRow(i + 1); return { status: 'success', message: 'Divisi dihapus' }; } } return { status: 'error', message: 'Data tidak ditemukan' }; } function savePosisi(payload) { var ss = SpreadsheetApp.getActiveSpreadsheet(); var sheet = ss.getSheetByName('Posisi'); var data = sheet.getDataRange().getValues(); var foundIndex = -1; for (var i = 1; i < data.length; i++) { if (String(data[i][0]) === String(payload.id)) { foundIndex = i + 1; break; } } var row = [payload.id, payload.nama]; if (foundIndex > 0) { sheet.getRange(foundIndex, 1, 1, row.length).setValues([row]); } else { sheet.appendRow(row); } return { status: 'success', message: 'Posisi tersimpan' }; } function deletePosisi(payload) { var ss = SpreadsheetApp.getActiveSpreadsheet(); var sheet = ss.getSheetByName('Posisi'); var data = sheet.getDataRange().getValues(); for (var i = 1; i < data.length; i++) { if (String(data[i][0]) === String(payload.id)) { sheet.deleteRow(i + 1); return { status: 'success', message: 'Posisi dihapus' }; } } return { status: 'error', message: 'Data tidak ditemukan' }; } function getSheetDataJSON(sheet) { if (!sheet) return []; var data = sheet.getDataRange().getValues(); if (data.length <= 1) return []; var headers = data[0]; var result = []; for (var i = 1; i < data.length; i++) { var obj = {}; for (var j = 0; j < headers.length; j++) { obj[headers[j]] = data[i][j]; } result.push(obj); } return result; } function responseJSON(data) { return ContentService.createTextOutput(JSON.stringify(data)) .setMimeType(ContentService.MimeType.JSON); } ```eor ### Langkah Singkat Aktivasi: 1. Copy kode `Code.gs` terbaru ke editor **Google Apps Script** Anda, lalu **Deploy** ulang sebagai Web App baru (*Execute as: Me*, *Who has access: Anyone*). 2. Di file `index.html`, ganti baris: ```javascript const APP_URL = "URL_HASIL_DEPLOY_BARU_KAMU"; let PREVIEW_MODE = false; ``` 3. Buka aplikasi, dan seluruh sinkronisasi data dengan Google Sheets akan berjalan lancar tanpa terhalang CORS/error parser JSON.