├── README.md └── Kode.gs /README.md: -------------------------------------------------------------------------------- 1 | # laporan-keuangan-bot 2 | [![Version](https://img.shields.io/badge/Version-2.0.1-green)]() 3 | [![Beta](https://img.shields.io/badge/Beta-orange)]()
4 | Laporan keuangan, pencataan pemasukan dan pengeluaran dengan Bot Telegram yang terintegrasi dengan Google Spreadsheet 5 | 6 | ### Video Tutorial 7 | https://youtu.be/sII577Ubv-E 8 | 9 |
10 | 11 | 12 | 13 | ### Yang ada di bot 14 | 1. Input pemasukan: /masuk [nominal] [#kategori] [item1, item2, keterangan dsb.]
15 | Contoh:
16 | /masuk 100000 #gaji angkut barang
17 | 2. Input pengeluaran: /keluar [nominal] [#kategori] [item1, item2, keterangan dsb.]
18 | Contoh:
19 | /keluar 50000 #makan roti dan kopi
20 | 3. Rekapitulasi: /rekap [tanggal/bulan] [tanggal/bulan (opsional)]
21 | Tanggal dan bulan berformat YYYY-MM-DD dan YYYY-MM
22 | Contoh:
23 | /rekap 2024-02-01
24 | /rekap 2024-02-01 2024-02-10
25 | /rekap 2024-02
26 | /rekap 2024-02 2024-06
27 | 28 | 29 | # Mulai 30 | 31 | ## Buat Bot telegram 32 | 1. Buka telegram search @BotFather 33 | 2. Create New Bot /newbot 34 | 3. Masukkan nama kemudian username. 35 | 4. Setelah berhasil maka akan mendapatkan Bot Token. 36 | 37 | ## Buat Spreadsheet 38 | Buat dua sheet untuk pemasukan dan pengeluaran dengan kolom: 39 | 1. _id 40 | 2. _date 41 | 3. Tanggal 42 | 4. Kategori 43 | 5. Item 44 | 6. Nominal 45 | 7. ReporterID 46 | 8. ReporterName 47 | 48 | ## Buat Apps Script 49 | 1. Copy Kode.gs 50 | 2. Sesuaikan Token, Spreadsheet URL, dan Nama Sheet untuk pemasukan dan pengeluaran 51 | 3. Tambahkan Library dengan ID: 1CZD-ai-ImkabBPSBVOqnFFWlXoA5kUEfoXvUXOC3uQHr_qpF1H7amHMr 52 | 4. Deploy sebagai Web app, dan simpan URL-nya 53 | 54 | ## Set webhook Bot Telegram 55 | 1. Buka di browser https[]()://api.telegram.org/bot[token]/setwebhook?url=[url hasil deploy]?users=ChatID1,ChatID2,...
56 | Sesuaikan ChatID* dengan user yang akan menggunakan bot, bisa lebih dari satu, pisahkan dengan koma. 57 | 58 | ## *Note: 59 | Untuk mendapatkan Chat ID, buka telegram, search @getYourID_bot atau https://t.me/getyourid_bot 60 | 61 | 62 | ## Contact 63 | Telegram: https://t.me/mastgh
64 | WhatsApp: +62 855-1000-113 65 | 66 | ## Donasi 67 | Paypal tegohsx@gmail.com
68 | BRI 000401061441500
69 | Jago 100310049829
70 | DANA/GOPAY 085290465350
71 | a/n Teguh S 72 | -------------------------------------------------------------------------------- /Kode.gs: -------------------------------------------------------------------------------- 1 | //CONFIG 2 | var BOT_TOKEN = "6642440655:AADRAWN3nXcuJf5xJkSQLhyJgQXGag08cLc" //BOT TOKEN ANDA 3 | var SS_URL = "https://docs.google.com/spreadsheets/d/1LArhvsBXZ8lJ3tzvx8DyYrFWwtRxlQsWQEFzdsyignw/edit#gid=0" //URL SPREADSHEET 4 | var CREDIT_SHEET_NAME = "Pemasukan" //NAMA SHEET PEMASUKAN 5 | var DEBIT_SHEET_NAME = "Pengeluaran" //NAMA SHEET PENGELUARAN 6 | 7 | 8 | //BEGIN 9 | const ss = SpreadsheetApp.openByUrl(SS_URL); 10 | const creditSheet = ss.getSheetByName(CREDIT_SHEET_NAME); 11 | const debitSheet = ss.getSheetByName(DEBIT_SHEET_NAME); 12 | const Credit = new Collection.Collect(creditSheet) 13 | const Debit = new Collection.Collect(debitSheet) 14 | 15 | function doGet(e) { 16 | return HtmlService.createHtmlOutput('

OK

') 17 | } 18 | 19 | function doPost(e) { 20 | const queryParameters = e.parameter; 21 | const usersQuery = queryParameters.users 22 | const validUsers = usersQuery.split(',') 23 | try { 24 | if (e.postData.type == "application/json") { 25 | let update = JSON.parse(e.postData.contents); 26 | if (update) { 27 | commands(update, validUsers) 28 | return true 29 | } 30 | } 31 | } catch (e) { 32 | Logger.log(e) 33 | } 34 | } 35 | 36 | function commands(update, validUsers) { 37 | 38 | const chatId = update.message.chat.id; 39 | const first_name = update.message.chat.first_name; 40 | const text = update.message.text || ''; 41 | const tanggal = new Date().toLocaleString(); 42 | const _date = new Date().toJSON() 43 | 44 | if (validUsers.includes(String(chatId))) { 45 | 46 | if (text.startsWith("/start")) { 47 | sendMessage({ 48 | chat_id: chatId, 49 | text: "Mulai laporan keuangan.\nPemasukan:\n/masuk [nominal] [#kategori] [item1, item2 dst]\nPengeluaran:\n/keluar [nominal] [#kategori] [item1, item2 dst]\n\nRekapitulasi: /rekap [tanggal/bulan] [tanggal/bulan (opsional)]\nTanggal dan bulan berformat YYYY-MM-DD dan YYYY-MM\nContoh: \n/rekap 2024-01-01\n/rekap 2024-01-01 2024-01-10\n/rekap 2024-01\n/rekap 2024-01 2024-06" 50 | }) 51 | } else if (text.startsWith("/masuk")) { 52 | const stext = text.split(' ') 53 | 54 | const nominal = Number(stext[1]); 55 | const kategori = stext[2].startsWith('#') ? stext[2].replace('#', '') : ''; 56 | 57 | stext.splice(0, 3); 58 | const item = stext.join(' ') 59 | 60 | if (nominal && kategori && item) { 61 | 62 | Credit.insert( 63 | { 64 | _date, 65 | Tanggal: tanggal, 66 | Kategori: kategori, 67 | Nominal: nominal, 68 | Item: item, 69 | ReporterID: chatId, 70 | ReporterName: first_name 71 | } 72 | ) 73 | 74 | sendMessage({ 75 | chat_id: chatId, 76 | text: 'Laporan pemasukan sukses.' 77 | }) 78 | 79 | } else { 80 | sendMessage({ 81 | chat_id: chatId, 82 | text: 'Gagal. Pastikan sesuai format. \n/masuk [harga] [#kategori] [item1, item2 dst]' 83 | }) 84 | } 85 | } else if (text.startsWith("/keluar")) { 86 | const stext = text.split(' ') 87 | 88 | const nominal = Number(stext[1]); 89 | const kategori = stext[2].startsWith('#') ? stext[2].replace('#', '') : ''; 90 | 91 | stext.splice(0, 3); 92 | const item = stext.join(' ') 93 | 94 | if (nominal && kategori && item) { 95 | 96 | Debit.insert( 97 | { 98 | _date, 99 | Tanggal: tanggal, 100 | Kategori: kategori, 101 | Nominal: nominal, 102 | Item: item, 103 | ReporterID: chatId, 104 | ReporterName: first_name 105 | } 106 | ) 107 | 108 | sendMessage({ 109 | chat_id: chatId, 110 | text: 'Laporan pengeluaran sukses.' 111 | }) 112 | 113 | } else { 114 | sendMessage({ 115 | chat_id: chatId, 116 | text: 'Gagal. Pastikan sesuai format. \n/keluar [harga] [#kategori] [item1, item2 dst]' 117 | }) 118 | } 119 | } else if (text.startsWith("/rekap")) { 120 | const stext = text.split(' ') 121 | stext.splice(0, 1); 122 | 123 | const oDateRange = Collection.generateDateRange(stext.join(' ')) 124 | const dataRange = oDateRange.dateRange 125 | const sumType = oDateRange.sumType 126 | const monthNames = ['Januari', 'Februari', 'Maret', 'April', 'Mei', 'Juni', 'Juli', 'Agustus', 'September', 'Oktober', 'November', 'Desember'] 127 | 128 | if (dataRange.length > 0) { 129 | let textToSend = "Rekapitulasi: \n" 130 | 131 | for (let _date of dataRange) { 132 | const pengeluaran = Debit.find( 133 | { 134 | _date: d => new Date(d) >= _date.from && new Date(d) < _date.to 135 | } 136 | ) 137 | 138 | const pemasukan = Credit.find( 139 | { 140 | _date: d => new Date(d) >= _date.from && new Date(d) < _date.to 141 | } 142 | ) 143 | 144 | let rekapMasuk = pemasukan.reduce((acc, item) => { 145 | if (!acc[item.Kategori]) { 146 | acc[item.Kategori] = 0; 147 | } 148 | acc[item.Kategori] += item.Nominal; 149 | return acc; 150 | }, {}); 151 | 152 | let rekapKeluar = pengeluaran.reduce((acc, item) => { 153 | if (!acc[item.Kategori]) { 154 | acc[item.Kategori] = 0; 155 | } 156 | acc[item.Kategori] += item.Nominal; 157 | return acc; 158 | }, {}); 159 | 160 | if (sumType == 'daily') { 161 | let dateTo = new Date(_date.to) 162 | dateTo.setDate(dateTo.getDate() - 1) 163 | textToSend += "Pemasukan " + _date.from.toLocaleDateString() + ' s.d ' + dateTo.toLocaleDateString() + "\n" 164 | textToSend += Object.keys(rekapMasuk).map((i) => `${i}: ${Number(rekapMasuk[i]).toLocaleString('id-ID')}`).join('\n') || '---' 165 | textToSend += '\nTotal: ' + Object.values(rekapMasuk).reduce((acc, value) => acc + value, 0).toLocaleString('id-ID'); 166 | textToSend += "\n" 167 | textToSend += "\n" 168 | textToSend += "Pengeluaran " + _date.from.toLocaleDateString() + ' s.d ' + dateTo.toLocaleDateString() + "\n" 169 | textToSend += Object.keys(rekapKeluar).map((i) => `${i}: ${Number(rekapKeluar[i]).toLocaleString('id-ID')}`).join('\n') || '---' 170 | textToSend += '\nTotal: ' + Object.values(rekapKeluar).reduce((acc, value) => acc + value, 0).toLocaleString('id-ID'); 171 | textToSend += "\n" 172 | textToSend += "\n" 173 | 174 | } 175 | else { 176 | textToSend += "Pemasukan bulan " + monthNames[_date.from.getMonth()] + ' ' + _date.from.getFullYear() + "\n" 177 | 178 | textToSend += Object.keys(rekapMasuk).map((i) => `${i}: ${Number(rekapMasuk[i]).toLocaleString('id-ID')}`).join('\n') || '---' 179 | textToSend += '\nTotal: ' + Object.values(rekapMasuk).reduce((acc, value) => acc + value, 0).toLocaleString('id-ID'); 180 | textToSend += "\n" 181 | 182 | textToSend += "\n" 183 | 184 | textToSend += "Pengeluaran bulan " + monthNames[_date.from.getMonth()] + ' ' + _date.from.getFullYear() + "\n" 185 | 186 | textToSend += Object.keys(rekapKeluar).map((i) => `${i}: ${Number(rekapKeluar[i]).toLocaleString('id-ID')}`).join('\n') || '---' 187 | textToSend += '\nTotal: ' + Object.values(rekapKeluar).reduce((acc, value) => acc + value, 0).toLocaleString('id-ID'); 188 | textToSend += "\n" 189 | 190 | textToSend += "\n" 191 | } 192 | } 193 | 194 | sendMessage({ 195 | chat_id: chatId, 196 | text: textToSend 197 | }) 198 | 199 | } else { 200 | sendMessage({ 201 | chat_id: chatId, 202 | text: 'Gagal. Pastikan sesuai format. \n/rekap [tanggal/bulan] [tanggal/bulan (opsional)]\nTanggal dan bulan berformat YYYY-MM-DD dan YYYY-MM\nContoh: \n/rekap 2024-01-01\n/rekap 2024-01-01 2024-01-10\n/rekap 2024-01\n/rekap 2024-01 2024-06' 203 | }) 204 | } 205 | } 206 | } 207 | } 208 | 209 | function sendMessage(postdata) { 210 | var options = { 211 | 'method': 'post', 212 | 'contentType': 'application/json', 213 | 'payload': JSON.stringify(postdata), 214 | 'muteHttpExceptions': true 215 | }; 216 | UrlFetchApp.fetch('https://api.telegram.org/bot' + BOT_TOKEN + '/sendMessage', options); 217 | } 218 | --------------------------------------------------------------------------------