├── README.md
└── Kode.gs
/README.md:
--------------------------------------------------------------------------------
1 | # laporan-keuangan-bot
2 | []()
3 | []()
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 |
--------------------------------------------------------------------------------