Scaling API-first – The story of a global engineering organization
Do something in 5 with gas 3-simple invoicing app
1. do something useful with
Apps Script in 5 minutes
3.Simple invoicing app
Bruce McPherson
www.mcpher.com
2. Snippet objectives
● Use the lessons learned in ‘using a spreadsheet as a
database’
● Gets transactions and master data from database. I’m
using sheets as the database here.
● Calculates and generates personalized invoice emails
Libraries used
● database abstraction
● driver sheet
3. Add libraries to script
create a script
Open resources
Add references to libraries
Mrckbr9_w7PCphJtOzhzA_Cz3TLx7pV4j
MHfCjPQlweartW45xYs6hFai_d-phDA33
4. Take a copy of the test data
Product and Customer data
https://docs.google.com/spreadsheets/d/1EhgZ1-q9tP1u9BSEQPIQiSGozdfjsHn3zB1JfQGbftw/edit?usp=sharing
Transaction data
https://docs.google.com/spreadsheets/d/1gmOEvILq0wygW3iN9exe4P0iu0x2wgwlX25D_QHgq10/edit?usp=sharing
5. layout what you are going to do
function myFunction() {
// get customer handle
// get product handle
// get transactions handle
// get all the transaction data ready to be invoiced
// organize into customers/transactions
// now we have an array of customers and their associated transactions - join to masters
// produce and email invoice with multiple transactions per customer
}
6. create function for repeated patterns
/**
* open a sheet as a database
* @param {string} sheetName the sheetName
* @param {string} sheetId the spreadsheet id
* @return {DbAbstraction} the handle
*/
function getHandle ( sheetName, sheetId) {
// open spreadsheet as database
var handler = new cDbAbstraction.DbAbstraction (cDriverSheet, {
siloid:sheetName,
dbid:sheetId,
});
if (!handler.isHappy()) throw 'unable to open sheet';
return handler;
}
We’ll call this function for each
of the database tables we
need to open
7. Get handlers for each table
// get customer handle
var customerHandle = getHandle ('customers' , '1EhgZ1-q9tP1u9BSEQPIQiSGozdfjsHn3zB1JfQGbftw');
// get product handle
var productHandle = getHandle ('products' , '1EhgZ1-q9tP1u9BSEQPIQiSGozdfjsHn3zB1JfQGbftw');
// get transactions handle
var transactionHandle = getHandle ('transactions' , '1gmOEvILq0wygW3iN9exe4P0iu0x2wgwlX25D_QHgq10');
8. Get the transaction data and sort it
// get all the transaction data ready to be invoiced
var transactionResult = transactionHandle.query ({status:'delivered'},{sort:'customer id'});
if (transactionResult.handleCode < 0) throw transactionResult.handleError;
9. Reduce transactions
// organize into customers/transactions - data is already sorted
var customerTransactions = transactionResult.data.reduce ( function (p,c) {
var t = p.length ? p[p.length-1] : null;
if (!t || c['customer id'] !== t.cid) {
// its a new customer
p.push( t = {cid:c['customer id'], transactions:[]} );
}
t.transactions.push(c);
return p;
},[]);
Reduce to one object per
customer, so we can
consolidate transactions to a
single invoice
10. Join transactions to masters
// now we have an array of customers and their associated transactions - join to masters
customerTransactions.forEach ( function (d) {
var result = customerHandle.query ({"customer id":d.cid});
// just fail if customer not found
if ( result.handleCode < 0 || result.data.length !== 1 ) throw JSON.stringify(result);
d.customer = result.data.slice()[0];
Look up the master data from
customers and product table
and join to transactions
// get the product data
d.transactions.forEach (function (p){
var result = productHandle.query ({"product id":p['product id']});
if ( result.handleCode < 0 || result.data.length !== 1 ) throw JSON.stringify(result);
p.product = result.data.slice()[0];
});
});
11. Function to create email content
function getEmailContent (d) {
return '<div>'
+ d.customer.company + '<br>'
+ '<table><tbody><th>Product name</th><th>Quantity</th><th>Total</th>'
+ d.transactions.map(function(e) {
return '<tr><td>'+e.product['product name']
+'</td><td>'+e.quantity
+'</td><td>'+e.quantity*e.product['unit price']
+'</td></tr>';
}).join(‘’)
+ '<tr><td>Total</td><td></td><td>'
+ d.transactions.reduce(function(p,c) {
return p+c.quantity*c.product['unit price'];
},0)
+ '</td></tr>'
+ '</tbody></table>'
+ '</div>';
}
Very basic email invoice. You’d
probably want to spruce up
and use a proper template
12. Send email invoices
customerTransactions.forEach (function(d) {
MailApp.sendEmail({
to: d.customer.email,
subject: "Invoice from desktop liberation",
htmlBody: getEmailContent (d)
});
});
Very basic email invoice. You’d
probably want to spruce up
and use a proper template
13. Further homework
● Using what you learned in graduating to a
database, why not migrate some of the test
data to a database and redo.
● Incorporate a real email templating solution
like Romain Vialard’s YAMM
● Improve error handling
● Update database status when email is sent
14. Follow up materials
Take a copy of this script
Take a copy of these slides
Join me on G+, or the G+ community
More on desktop liberation
More on database abstraction