1. Ringkasan
Dalam codelab ini, Anda akan menggunakan Google Apps Script guna menulis Add-on Google Workspace untuk Gmail yang memungkinkan pengguna menambahkan data tanda terima dari email ke spreadsheet langsung dalam Gmail. Saat menerima tanda terima melalui email, pengguna akan membuka add-on yang secara otomatis mendapatkan informasi pengeluaran yang relevan dari email. Pengguna dapat mengedit informasi pengeluaran dan kemudian mengirimkannya untuk mencatat pengeluaran mereka ke dalam {i>spreadsheet<i} Google Sheets.
Yang akan Anda pelajari
- Membuat Add-on Google Workspace untuk Gmail menggunakan Google Apps Script
- Mengurai email dengan Google Apps Script
- Berinteraksi dengan Google Spreadsheet melalui Google Apps Script
- Simpan nilai pengguna menggunakan layanan Properti Google Apps Script
Yang Anda butuhkan
- Akses ke internet dan browser web
- Akun Google
- Beberapa pesan, sebaiknya tanda terima email, di Gmail
2. Mendapatkan kode contoh
Saat mengerjakan codelab ini, sebaiknya Anda merujuk ke versi kode yang berfungsi yang akan Anda tulis. Repositori GitHub berisi kode contoh yang dapat Anda gunakan sebagai referensi.
Untuk mendapatkan kode contoh, dari command line, jalankan:
git clone https://github.com/googleworkspace/gmail-add-on-codelab.git
3. Membuat add-on dasar
Mulailah dengan menulis kode untuk versi add-on sederhana yang menampilkan formulir pengeluaran bersama email.
Pertama, buat project Apps Script baru dan buka file manifesnya.
- Buka script.google.com. Dari sini, Anda dapat membuat, mengelola, dan memantau project Apps Script Anda.
- Untuk membuat project baru, klik New Project di kiri atas. Project baru akan terbuka dengan file default bernama
Code.gs
. BiarkanCode.gs
sendirian untuk saat ini. Anda akan mengerjakannya nanti. - Klik Project tanpa judul, beri nama project Anda dengan Expense It!, lalu klik Rename.
- Di sebelah kiri, klik Project Settings .
- Pilih tombol Tampilkan "appscript.json" file manifes dalam editor".
- Klik Editor .
- Untuk membuka file manifes, klik
appscript.json
di sebelah kiri.
Di appscript.json
, tentukan metadata yang terkait dengan add-on, seperti namanya dan izin yang diperlukan add-on. Ganti konten appsscript.json
dengan setelan konfigurasi ini:
{
"timeZone": "GMT",
"oauthScopes": [
"https://www.googleapis.com/auth/gmail.addons.execute"
],
"gmail": {
"name": "Expense It!",
"logoUrl": "https://www.gstatic.com/images/icons/material/system/1x/receipt_black_24dp.png",
"contextualTriggers": [{
"unconditional": {
},
"onTriggerFunction": "getContextualAddOn"
}],
"primaryColor": "#41f470",
"secondaryColor": "#94f441"
}
}
Berikan perhatian khusus pada bagian manifes yang disebut contextualTriggers
. Bagian manifes ini mengidentifikasi fungsi yang ditentukan pengguna untuk dipanggil saat add-on pertama kali diaktifkan. Dalam hal ini, fungsi ini memanggil getContextualAddOn
, yang mendapatkan detail tentang email yang dibuka dan menampilkan serangkaian kartu untuk ditampilkan kepada pengguna.
Untuk membuat fungsi getContextualAddOn
, ikuti langkah-langkah berikut:
- Di sebelah kiri, arahkan kursor ke
Code.gs
, lalu klik Menu > Ganti nama. - Ketikkan
GetContextualAddOn
, lalu tekan tombolEnter
. Apps Script secara otomatis menambahkan.gs
ke nama file, sehingga Anda tidak perlu mengetikkan ekstensi file. Jika Anda mengetikGetContextualAddOn.gs
, Apps Script akan memberi nama file Anda sebagaiGetContextualAddOn.gs.gs
. - Di
GetContextualAddOn.gs
, ganti kode default dengan fungsigetContextualAddOn
:
/**
* Returns the contextual add-on data that should be rendered for
* the current e-mail thread. This function satisfies the requirements of
* an 'onTriggerFunction' and is specified in the add-on's manifest.
*
* @param {Object} event Event containing the message ID and other context.
* @returns {Card[]}
*/
function getContextualAddOn(event) {
var card = CardService.newCardBuilder();
card.setHeader(CardService.newCardHeader().setTitle('Log Your Expense'));
var section = CardService.newCardSection();
section.addWidget(CardService.newTextInput()
.setFieldName('Date')
.setTitle('Date'));
section.addWidget(CardService.newTextInput()
.setFieldName('Amount')
.setTitle('Amount'));
section.addWidget(CardService.newTextInput()
.setFieldName('Description')
.setTitle('Description'));
section.addWidget(CardService.newTextInput()
.setFieldName('Spreadsheet URL')
.setTitle('Spreadsheet URL'));
card.addSection(section);
return [card.build()];
}
Setiap antarmuka pengguna add-on Add-on Google Workspace terdiri dari kartu yang dibagi menjadi satu atau beberapa bagian, masing-masing berisi widget yang dapat menampilkan dan mendapatkan informasi dari pengguna. Fungsi getContextualAddOn
membuat satu kartu yang mendapatkan detail tentang pengeluaran yang ditemukan di email. Kartu ini memiliki satu bagian yang berisi kolom input teks untuk data yang relevan. Fungsi ini menampilkan array kartu add-on. Dalam hal ini, array yang ditampilkan hanya menyertakan satu kartu.
Sebelum men-deploy Expense It. add-on, Anda memerlukan Project Google Cloud Platform (GCP), yang digunakan project Apps Script untuk mengelola otorisasi, layanan lanjutan, dan detail lainnya. Untuk mempelajari lebih lanjut, buka Project Google Cloud Platform.
Untuk men-deploy dan menjalankan add-on, ikuti langkah-langkah berikut:
- Buka project GCP Anda, dan salin nomor project-nya.
- Dari project Apps Script Anda, di sebelah kiri, klik Project Settings .
- Pada "Google Cloud Platform (GCP) Project", klik Change project.
- Masukkan nomor project project GCP Anda, lalu klik Set project.
- Klik Deploy > Test deployment.
- Pastikan jenis deployment adalah Add-on Google Workspace. Jika perlu, di bagian atas dialog, klik Aktifkan jenis deployment , lalu pilih Add-on Google Workspace sebagai jenis deployment.
- Di samping Aplikasi: Gmail, klik Instal.
- Klik Selesai.
Sekarang Anda dapat melihat add-on tersebut di kotak masuk Gmail.
- Di komputer, buka Gmail.
- Di panel samping kanan, ikon Expense It! Add-on muncul. Anda mungkin perlu mengklik Add-on Lainnya untuk menemukannya.
- Buka email, sebaiknya tanda terima yang berisi pengeluaran.
- Untuk membuka add-on, di panel samping kanan, klik Belanjakan. .
- Berikan Biaya! akses ke akun Google Anda dengan mengklik Izinkan Akses dan ikuti petunjuknya.
Add-on menampilkan formulir sederhana bersama dengan pesan Gmail yang terbuka. Kode ini belum melakukan apa pun, tetapi Anda akan membangun fungsinya di bagian berikutnya.
Untuk melihat pembaruan add-on saat Anda melanjutkan lab ini, Anda hanya perlu menyimpan kode dan memuat ulang Gmail. Deployment tambahan tidak diperlukan.
4. Mengakses pesan email
Tambahkan kode yang mengambil konten email dan memodularisasi kode untuk lebih banyak organisasi.
Di samping File, klik Tambahkan > Skrip dan buat file bernama Cards
. Buat file skrip kedua bernama Helpers
. Cards.gs
membuat kartu dan menggunakan fungsi dari Helpers.gs
untuk mengisi kolom dalam formulir berdasarkan konten email.
Ganti kode default di Cards.gs
dengan kode ini:
var FIELDNAMES = ['Date', 'Amount', 'Description', 'Spreadsheet URL'];
/**
* Creates the main card users see with form inputs to log expenses.
* Form can be prefilled with values.
*
* @param {String[]} opt_prefills Default values for each input field.
* @param {String} opt_status Optional status displayed at top of card.
* @returns {Card}
*/
function createExpensesCard(opt_prefills, opt_status) {
var card = CardService.newCardBuilder();
card.setHeader(CardService.newCardHeader().setTitle('Log Your Expense'));
if (opt_status) {
if (opt_status.indexOf('Error: ') == 0) {
opt_status = '<font color=\'#FF0000\'>' + opt_status + '</font>';
} else {
opt_status = '<font color=\'#228B22\'>' + opt_status + '</font>';
}
var statusSection = CardService.newCardSection();
statusSection.addWidget(CardService.newTextParagraph()
.setText('<b>' + opt_status + '</b>'));
card.addSection(statusSection);
}
var formSection = createFormSection(CardService.newCardSection(),
FIELDNAMES, opt_prefills);
card.addSection(formSection);
return card;
}
/**
* Creates form section to be displayed on card.
*
* @param {CardSection} section The card section to which form items are added.
* @param {String[]} inputNames Names of titles for each input field.
* @param {String[]} opt_prefills Default values for each input field.
* @returns {CardSection}
*/
function createFormSection(section, inputNames, opt_prefills) {
for (var i = 0; i < inputNames.length; i++) {
var widget = CardService.newTextInput()
.setFieldName(inputNames[i])
.setTitle(inputNames[i]);
if (opt_prefills && opt_prefills[i]) {
widget.setValue(opt_prefills[i]);
}
section.addWidget(widget);
}
return section;
}
Fungsi createExpensesCard
menggunakan array nilai untuk mengisi otomatis formulir sebagai argumen opsional. Fungsi ini dapat menampilkan pesan status opsional, yang berwarna merah jika status diawali dengan "Error:", dan berwarna hijau. Alih-alih menambahkan setiap kolom ke formulir secara manual, fungsi bantuan yang disebut createFormSection
akan melakukan loop melalui proses pembuatan widget input teks, menetapkan setiap nilai default dengan setValue
, lalu menambahkan widget tersebut ke bagiannya masing-masing pada kartu.
Sekarang, ganti kode default di Helpers.gs
dengan kode ini:
/**
* Finds largest dollar amount from email body.
* Returns null if no dollar amount is found.
*
* @param {Message} message An email message.
* @returns {String}
*/
function getLargestAmount(message) {
return 'TODO';
}
/**
* Determines date the email was received.
*
* @param {Message} message An email message.
* @returns {String}
*/
function getReceivedDate(message) {
return 'TODO';
}
/**
* Determines expense description by joining sender name and message subject.
*
* @param {Message} message An email message.
* @returns {String}
*/
function getExpenseDescription(message) {
return 'TODO';
}
/**
* Determines most recent spreadsheet URL.
* Returns null if no URL was previously submitted.
*
* @returns {String}
*/
function getSheetUrl() {
return 'TODO';
}
Fungsi di Helpers.gs
dipanggil oleh getContextualAddon
untuk menentukan nilai yang terisi otomatis di formulir. Untuk saat ini, fungsi ini hanya akan menampilkan string "TODO" karena Anda akan mengimplementasikan logika pengisian otomatis di langkah berikutnya.
Selanjutnya, perbarui kode di GetContextualAddon.gs
agar memanfaatkan kode di Cards.gs
dan Helpers.gs
. Ganti kode di GetContextualAddon.gs
dengan kode ini:
/**
* Copyright 2017 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Returns the contextual add-on data that should be rendered for
* the current e-mail thread. This function satisfies the requirements of
* an 'onTriggerFunction' and is specified in the add-on's manifest.
*
* @param {Object} event Event containing the message ID and other context.
* @returns {Card[]}
*/
function getContextualAddOn(event) {
var message = getCurrentMessage(event);
var prefills = [getReceivedDate(message),
getLargestAmount(message),
getExpenseDescription(message),
getSheetUrl()];
var card = createExpensesCard(prefills);
return [card.build()];
}
/**
* Retrieves the current message given an action event object.
* @param {Event} event Action event object
* @return {Message}
*/
function getCurrentMessage(event) {
var accessToken = event.messageMetadata.accessToken;
var messageId = event.messageMetadata.messageId;
GmailApp.setCurrentMessageAccessToken(accessToken);
return GmailApp.getMessageById(messageId);
}
Perhatikan fungsi getCurrentMessage
baru, yang menggunakan acara yang disediakan oleh Gmail untuk membaca pesan yang saat ini dibuka pengguna. Agar fungsi ini dapat berfungsi, tambahkan cakupan tambahan ke manifes skrip yang mengizinkan akses hanya baca ke pesan Gmail.
Di appscript.json
, perbarui oauthScopes
agar juga meminta cakupan https://www.googleapis.com/auth/gmail.addons.current.message.readonly
.
"oauthScopes": [
"https://www.googleapis.com/auth/gmail.addons.execute",
"https://www.googleapis.com/auth/gmail.addons.current.message.readonly"
],
Di Gmail, jalankan add-on Anda, dan beri otorisasi akses untuk Membiayai Biaya. untuk melihat pesan email. Kolom formulir kini telah diisi dengan "TODO".
5. Berinteraksi dengan Google Spreadsheet
Menghabiskan Biaya! {i>add-on<i} memiliki formulir bagi pengguna untuk memasukkan detail tentang pengeluaran, tetapi detail tersebut tidak dapat dimasukkan ke mana pun. Mari kita tambahkan tombol yang mengirimkan data formulir ke Spreadsheet Google.
Untuk menambahkan tombol, kita akan menggunakan class ButtonSet. Untuk berinteraksi dengan Google Spreadsheet, kita akan menggunakan layanan Google Spreadsheet.
Ubah createFormSection
untuk menampilkan tombol berlabel "Kirim" sebagai bagian dari
bagian formulir kartu. Lakukan langkah-langkah berikut:
- Buat tombol teks menggunakan
CardService.newTextButton()
, dengan memberi label pada tombol "Kirim" menggunakanCardService.TextButton.setText()
. - Desain tombol sedemikian rupa sehingga saat diklik, tindakan
submitForm
berikut akan dipanggil melaluiCardService.TextButton.setOnClickAction()
:
/**
* Logs form inputs into a spreadsheet given by URL from form.
* Then displays edit card.
*
* @param {Event} e An event object containing form inputs and parameters.
* @returns {Card}
*/
function submitForm(e) {
var res = e['formInput'];
try {
FIELDNAMES.forEach(function(fieldName) {
if (! res[fieldName]) {
throw 'incomplete form';
}
});
var sheet = SpreadsheetApp
.openByUrl((res['Spreadsheet URL']))
.getActiveSheet();
sheet.appendRow(objToArray(res, FIELDNAMES.slice(0, FIELDNAMES.length - 1)));
return createExpensesCard(null, 'Logged expense successfully!').build();
}
catch (err) {
if (err == 'Exception: Invalid argument: url') {
err = 'Invalid URL';
res['Spreadsheet URL'] = null;
}
return createExpensesCard(objToArray(res, FIELDNAMES), 'Error: ' + err).build();
}
}
/**
* Returns an array corresponding to the given object and desired ordering of keys.
*
* @param {Object} obj Object whose values will be returned as an array.
* @param {String[]} keys An array of key names in the desired order.
* @returns {Object[]}
*/
function objToArray(obj, keys) {
return keys.map(function(key) {
return obj[key];
});
}
- Buat widget kumpulan tombol menggunakan
CardService.newButtonSet()
dan tambahkan tombol teks Anda ke tombol yang disetel denganCardService.ButtonSet.addButton()
. - Tambahkan widget set tombol ke bagian formulir kartu menggunakan
CardService.CardSection.addWidget()
.
Hanya dengan beberapa baris kode, kita dapat membuka {i>spreadsheet<i} dengan URL-nya dan kemudian menambahkan baris data ke {i>sheet<i} tersebut. Perhatikan bahwa input formulir diteruskan ke fungsi sebagai bagian dari peristiwa e
, dan kita akan memeriksa apakah pengguna telah menyediakan semua kolom. Dengan asumsi tidak terjadi error, kami membuat kartu pengeluaran kosong dengan status yang baik. Jika kami mendapati error, kami akan mengembalikan kartu asli yang terisi bersama dengan pesan error tersebut. Fungsi bantuan objToArray
memudahkan konversi respons formulir menjadi array, yang kemudian dapat ditambahkan ke spreadsheet.
Terakhir, perbarui bagian oauthScopes
di appsscript.json
lagi untuk meminta cakupan https://www.googleapis.com/auth/spreadsheets
. Jika diberi otorisasi, cakupan ini memungkinkan add-on membaca dan mengubah Google Spreadsheet pengguna.
"oauthScopes": [
"https://www.googleapis.com/auth/gmail.addons.execute",
"https://www.googleapis.com/auth/gmail.addons.current.message.readonly",
"https://www.googleapis.com/auth/spreadsheets"
],
Jika Anda belum membuat spreadsheet baru, buatlah di https://docs.google.com/spreadsheets/.
Sekarang, jalankan kembali add-on dan coba kirimkan formulir. Pastikan Anda memasukkan URL lengkap URL tujuan ke kolom formulir URL Spreadsheet.
6. Menyimpan nilai dengan layanan Properties
Sering kali, pengguna akan mencatat banyak pengeluaran ke {i>spreadsheet<i} yang sama, sehingga akan lebih praktis untuk menawarkan URL {i>spreadsheet <i}terbaru sebagai nilai {i>default<i} dalam kartu. Untuk mengetahui URL spreadsheet terbaru, kami harus menyimpan informasi tersebut setiap kali add-on digunakan.
Layanan Properti memungkinkan kita menyimpan key-value pair. Dalam kasus ini, kunci yang masuk akal adalah "SPREADSHEET_URL" sedangkan nilainya akan berupa URL itu sendiri. Untuk menyimpan nilai seperti itu, Anda harus mengubah submitForm
di Cards.gs
sehingga URL spreadsheet disimpan sebagai properti setelah menambahkan baris baru ke sheet.
Perhatikan bahwa properti dapat memiliki salah satu dari tiga cakupan: skrip, pengguna, atau dokumen. Cakupan dokumen tidak berlaku untuk add-on Gmail, meskipun relevan dengan jenis add-on terpisah saat menyimpan informasi khusus untuk Dokumen atau Spreadsheet Google tertentu. Untuk add-on kami, perilaku yang diinginkan adalah agar seseorang melihat spreadsheet terbarunya sendiri (bukan spreadsheet terbaru orang lain) sebagai opsi default pada formulir. Akibatnya, kita memilih cakupan pengguna, bukan cakupan script.
Gunakan PropertiesService.getUserProperties().setProperty()
untuk menyimpan URL spreadsheet. Tambahkan kode berikut ke submitForm
di Cards.gs
:
PropertiesService.getUserProperties().setProperty('SPREADSHEET_URL',
res['Spreadsheet URL']);
Kemudian, ubah fungsi getSheetUrl
di Helpers.gs
untuk menampilkan properti tersimpan sehingga pengguna akan melihat URL terbaru setiap kali mereka menggunakan add-on. Gunakan PropertiesService.getUserProperties().getProperty()
untuk mendapatkan nilai properti.
/**
* Determines most recent spreadsheet URL.
* Returns null if no URL was previously submitted.
*
* @returns {String}
*/
function getSheetUrl() {
return PropertiesService.getUserProperties().getProperty('SPREADSHEET_URL');
}
Terakhir, untuk mengakses layanan Properti, skrip juga harus diotorisasi. Tambahkan cakupan https://www.googleapis.com/auth/script.storage
ke manifes seperti sebelumnya agar add-on Anda dapat membaca dan menulis informasi properti.
7. Mengurai pesan Gmail
Untuk menghemat waktu mari kita isi formulir tersebut dengan informasi
yang relevan tentang pengeluaran dari email. Kita telah membuat fungsi di Helpers.gs
yang menjalankan peran ini, tetapi sejauh ini kita hanya menampilkan "TODO" untuk tanggal, jumlah, dan deskripsi pengeluaran.
Misalnya, kita bisa mendapatkan tanggal email diterima dan menggunakannya sebagai nilai default untuk tanggal pengeluaran.
/**
* Determines date the email was received.
*
* @param {Message} message - The message currently open.
* @returns {String}
*/
function getReceivedDate(message) {
return message.getDate().toLocaleDateString();
}
Implementasikan dua fungsi yang tersisa:
getExpenseDescription
mungkin mengharuskan penggabungan nama pengirim dan subjek pesan, meskipun ada cara yang lebih canggih untuk mengurai isi pesan dan memberikan deskripsi yang lebih akurat.- Untuk
getLargestAmount
, sebaiknya cari simbol tertentu yang terkait dengan uang. Tanda terima sering kali mencantumkan beberapa nilai, seperti pajak dan biaya lainnya. Pikirkan bagaimana Anda dapat mengidentifikasi jumlah yang tepat. Ekspresi reguler juga berguna.
Jika Anda memerlukan inspirasi tambahan, baca dokumentasi referensi untuk GmailMessage
atau lihat kode solusi yang Anda download di awal codelab. Setelah membuat implementasi Anda sendiri untuk semua fungsi di Helpers.gs
, cobalah add-on Anda. Buka tanda terima dan mulailah mencatatnya di {i>spreadsheet<i}.
8. Mengosongkan formulir dengan tindakan kartu
Apa yang terjadi jika Belanjakan! salah mengidentifikasi pengeluaran dalam email terbuka dan mengisi formulir dengan informasi yang salah? Pengguna menghapus formulir. Class CardAction memungkinkan kita menentukan fungsi yang dipanggil saat tindakan diklik. Mari kita gunakan untuk memberi pengguna cara cepat menghapus formulir.
Ubah createExpensesCard
sehingga kartu yang ditampilkan memiliki tindakan kartu berlabel "Hapus formulir" dan saat diklik, fungsi clearForm
berikut akan dipanggil, yang dapat Anda tempelkan ke Cards.gs
. Anda harus meneruskan opt_status
sebagai parameter yang bernama "Status" ke tindakan untuk memastikan bahwa saat formulir dihapus, pesan status tetap ada. Perhatikan bahwa parameter opsional untuk tindakan harus berjenis Object.<string, string>, jadi jika opt_status
tidak tersedia, Anda harus meneruskan {'Status' : ''}
.
/**
* Recreates the main card without prefilled data.
*
* @param {Event} e An event object containing form inputs and parameters.
* @returns {Card}
*/
function clearForm(e) {
return createExpensesCard(null, e['parameters']['Status']).build();
}
9. Buat spreadsheet
Selain menggunakan Google Apps Script untuk mengedit spreadsheet yang ada, Anda dapat membuat spreadsheet yang benar-benar baru secara terprogram. Untuk {i>add-on<i} kita, izinkan pengguna untuk membuat {i>spreadsheet<i} untuk pengeluaran. Untuk memulai, tambahkan bagian kartu berikut ke kartu yang ditampilkan createExpensesCard
.
var newSheetSection = CardService.newCardSection();
var sheetName = CardService.newTextInput()
.setFieldName('Sheet Name')
.setTitle('Sheet Name');
var createExpensesSheet = CardService.newAction()
.setFunctionName('createExpensesSheet');
var newSheetButton = CardService.newTextButton()
.setText('New Sheet')
.setOnClickAction(createExpensesSheet);
newSheetSection.addWidget(sheetName);
newSheetSection.addWidget(CardService.newButtonSet().addButton(newSheetButton));
card.addSection(newSheetSection);
Sekarang, ketika pengguna mengklik "Sheet Baru" add-on akan menghasilkan spreadsheet baru yang diformat dengan baris header yang dibekukan sedemikian rupa sehingga selalu terlihat. Pengguna menentukan judul untuk spreadsheet baru dalam formulir, meskipun menyertakan nilai default jika formulir tersebut kosong mungkin merupakan pilihan yang baik. Dalam penerapan createExpensesSheet
, tampilkan kartu yang hampir sama dengan kartu yang ada, dengan penambahan pesan status yang sesuai serta pengisian kolom URL dengan URL spreadsheet baru.
10. Selamat!
Anda telah berhasil merancang dan menerapkan add-on Gmail yang membebankan biaya dalam email dan membantu pengguna mencatat biaya ke spreadsheet hanya dalam hitungan detik. Anda telah menggunakan Google Apps Script untuk berinteraksi dengan beberapa Google API dan mempertahankan data di antara beberapa eksekusi add-on.
Kemungkinan Peningkatan
Biarkan imajinasi Anda memandu saat Anda meningkatkan Biaya Pengeluaran!, tetapi berikut adalah beberapa ide untuk membuat produk yang lebih berguna:
- Tautkan ke {i>spreadsheet<i} setelah pengguna mencatat pengeluaran
- Menambahkan kemampuan untuk mengedit/mengurungkan pencatatan pengeluaran
- Integrasikan API eksternal agar pengguna dapat melakukan pembayaran dan meminta uang