do something useful with 
Apps Script in 5 minutes 
3.Simple invoicing app 
Bruce McPherson 
www.mcpher.com
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
Add libraries to script 
create a script 
Open resources 
Add references to libraries 
Mrckbr9_w7PCphJtOzhzA_Cz3TLx7pV4j 
MHfCjPQlweartW45xYs6hFai_d-phDA33
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
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 
}
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
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');
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;
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
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]; 
}); 
});
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
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
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
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

Do something in 5 with gas 3-simple invoicing app

  • 1.
    do something usefulwith 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 toscript create a script Open resources Add references to libraries Mrckbr9_w7PCphJtOzhzA_Cz3TLx7pV4j MHfCjPQlweartW45xYs6hFai_d-phDA33
  • 4.
    Take a copyof 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 youare 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 forrepeated 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 foreach 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 transactiondata 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 tomasters // 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 createemail 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