(+91)7275894867 [email protected]
OutRightCRM Login
📅 Published on August 14, 2024 ✏️ Updated on April 22, 2025

Automated Invoicing from Spreadsheets

Author Avatar
Author
Editorial Team

Automated Invoicing from Spreadsheets

Automated invoicing from spreadsheets uses Google Apps Script to generate invoices from Google Sheets data. It reads client info, services, and amounts, then fills a Google Docs or PDF template. Invoices are saved, emailed, or printed automatically—saving time, reducing errors, and streamlining billing for freelancers, businesses, and agencies.
  1. Step 1: Click on the Copy button to copy the code snippet.
  2. Step 2: Paste the copied code into your project’s script editor.

Apps Scripts Blog

Read Blog

Apps Scripts Code

function onOpen() {
  const ui = SpreadsheetApp.getUi();
  ui.createMenu("Outright")
    .addItem("Create PDF Links", "createPdfLinks")
    .addItem("Send PDFs", "sendPdfs")
    .addToUi();


  createHeadersIfNeeded();
}


function createHeadersIfNeeded() {
  const properties = PropertiesService.getScriptProperties();
  const headersSet = properties.getProperty("headersSet");


  if (!headersSet) {
    createHeaders();
    properties.setProperty("headersSet", "true");
  }
}


function createHeaders() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  const headers = [
    "INVOICE_DATE", "DUE_DATE", "INVOICE_NUMBER", "Contact person",
    "contact_company", "Country", "my_company_details", "ITEM_1", "cost_1", "ITEM_2",
    "cost_2", "ITEM_3", "cost_3", "Total_cost", "my_bank_details", "Email", "status", "Final invoice"
  ];


  const existingHeaders = sheet.getRange("1:1").getValues()[0];
  let headersToSet = [];


  headers.forEach((header, index) => {
    if (!existingHeaders.includes(header)) {
      headersToSet.push([header]);
    } else {
      headersToSet.push([existingHeaders[existingHeaders.indexOf(header)]]);
    }
  });


  sheet.getRange(1, 1, 1, headersToSet.length).setValues([headersToSet.flat()]);
  const headerRange = sheet.getRange(1, 1, 1, headersToSet.length);
  headerRange.setFontWeight("bold");
  headerRange.setFontSize(14);
}


function getTemplateId() {
  const templateSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("invoice templates");
  if (!templateSheet) {
    throw new Error('Sheet "invoice templates" not found');
  }
  const templateUrl = templateSheet.getRange("B2").getValue();  // Assuming the template URL is in cell B2
  const templateId = templateUrl.match(/[-\w]{25,}/);  // Extract the ID from the URL
  if (!templateId) {
    throw new Error('Template ID not found in the provided URL');
  }
  return templateId[0];
}


function createPdfLinks() {
  try {
    const copiedSheet = createMonthlySheetCopy(); // Create a copy of the master sheet before creating PDF links


    var ss = SpreadsheetApp.getActiveSpreadsheet();
    var masterSheet = ss.getSheetByName("master-clients");
    var sheet = copiedSheet;
    if (!sheet) {
      throw new Error('Copied sheet not found');
    }


    var dataRange = sheet.getDataRange();
    var dataValues = dataRange.getValues();
    var headers = dataValues[0]; // Get the headers from the first row
    var templateFileId = getTemplateId();  // Get the template ID from the "invoice templates" sheet
    var templateDoc = DriveApp.getFileById(templateFileId);
    var templateName = templateDoc.getName();
    var folder = DriveApp.createFolder("Generated Documents");


    var currentDate = new Date();
    var currentMonth = new Intl.DateTimeFormat('en-US', { month: 'long' }).format(currentDate);
    var lastTwoDigitsOfYear = String(currentDate.getFullYear()).slice(-2);
    var currentDay = ('0' + currentDate.getDate()).slice(-2);
    var invoiceName = currentMonth + ' ' + currentDay + ' Invoice from Outright Systems';


    for (var i = 1; i < dataValues.length; i++) { var row = dataValues[i]; var clientNumber = row[headers.indexOf("INVOICE_NUMBER")]; var invoiceNumber = currentDate.getFullYear() + '.' + ('0' + (currentDate.getMonth() + 1)).slice(-2) + '.' + clientNumber; row[headers.indexOf("INVOICE_NUMBER")] = invoiceNumber; var invoiceDate = row[headers.indexOf("INVOICE_DATE")]; if (!invoiceDate) { invoiceDate = currentDate; row[headers.indexOf("INVOICE_DATE")] = invoiceDate; sheet.getRange(i + 1, headers.indexOf("INVOICE_DATE") + 1).setValue(Utilities.formatDate(invoiceDate, Session.getScriptTimeZone(), "dd MMM yyyy")); masterSheet.getRange(i + 1, headers.indexOf("INVOICE_DATE") + 1).setValue(Utilities.formatDate(invoiceDate, Session.getScriptTimeZone(), "dd MMM yyyy")); } var dueDate = row[headers.indexOf("DUE_DATE")]; if (!dueDate) { var nextMonthDate = new Date(invoiceDate); nextMonthDate.setMonth(nextMonthDate.getMonth() + 1); nextMonthDate.setDate(1); row[headers.indexOf("DUE_DATE")] = nextMonthDate; sheet.getRange(i + 1, headers.indexOf("DUE_DATE") + 1).setValue(Utilities.formatDate(nextMonthDate, Session.getScriptTimeZone(), "dd MMM yyyy")); masterSheet.getRange(i + 1, headers.indexOf("DUE_DATE") + 1).setValue(Utilities.formatDate(nextMonthDate, Session.getScriptTimeZone(), "dd MMM yyyy")); } var cost1 = parseFloat(row[headers.indexOf("cost_1")]) || 0; var cost2 = parseFloat(row[headers.indexOf("cost_2")]) || 0; var cost3 = parseFloat(row[headers.indexOf("cost_3")]) || 0; var totalCost = cost1 + cost2 + cost3; row[headers.indexOf("Total_cost")] = totalCost + " USD"; sheet.getRange(i + 1, headers.indexOf("Total_cost") + 1).setValue(totalCost + " USD"); masterSheet.getRange(i + 1, headers.indexOf("Total_cost") + 1).setValue(totalCost + " USD"); var templateCopy = templateDoc.makeCopy(); var doc = DocumentApp.openById(templateCopy.getId()); var body = doc.getBody();
headers.forEach((header, index) => {
        var placeholder = "{{" + header.toLowerCase() + "}}";
        var value = row[index];
        if (typeof value === 'object' && value instanceof Date) {
          value = Utilities.formatDate(value, Session.getScriptTimeZone(), "dd MMM yyyy");
        }
        body.replaceText(placeholder, value);
      });


      doc.saveAndClose();


      var newFileName = templateName + " - " + invoiceNumber;
      var copiedFile = DriveApp.getFileById(templateCopy.getId());
      copiedFile.setName(newFileName);


      var pdfFile = copiedFile.getAs(MimeType.PDF);
      var finalPdf = folder.createFile(pdfFile);
      finalPdf.setName(newFileName + ".pdf");


      var pdfLink = finalPdf.getUrl();
      sheet.getRange(i + 1, headers.indexOf("Final invoice") + 1).setValue(pdfLink);


      // Update status to "Created" in both sheets
      sheet.getRange(i + 1, headers.indexOf("status") + 1).setValue("Created");
      masterSheet.getRange(i + 1, headers.indexOf("status") + 1).setValue("Created");


      Logger.log("PDF created: " + pdfLink);


      copiedFile.setTrashed(true);
    }


    SpreadsheetApp.getUi().alert("PDF Links successfully created.");
  } catch (error) {
    SpreadsheetApp.getUi().alert("Error: " + error.message);
    Logger.log("Error: " + error.message);
  }
}








function sendPdfs() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var masterSheet = ss.getSheetByName("master-clients");
  if (!masterSheet) {
    throw new Error('Sheet "master-clients" not found');
  }
  var currentDate = new Date();
  var currentMonth = new Intl.DateTimeFormat('en-US', { month: 'long' }).format(currentDate);
  var lastTwoDigitsOfYear = String(currentDate.getFullYear()).slice(-2);
  var currentDay = ('0' + currentDate.getDate()).slice(-2);
  var newSheetName = currentMonth + " " + lastTwoDigitsOfYear + " Invoices";
  var copiedSheet = ss.getSheetByName(newSheetName);
  if (!copiedSheet) {
    throw new Error('Copied sheet not found');
  }


  var dataRange = copiedSheet.getDataRange();
  var dataValues = dataRange.getValues();
  var headers = dataValues[0]; // Get the headers from the first row
  var baseInvoiceName = currentMonth + ' ' + currentDay + ' Invoice from Outright Systems';


  for (var i = 1; i < dataValues.length; i++) {
    var row = dataValues[i];
    var email = row[headers.indexOf("Email")];
    var pdfLink = row[headers.indexOf("Final invoice")];


    Logger.log("Processing row: " + (i + 1));
    Logger.log("Email: " + email);
    Logger.log("PDF Link: " + pdfLink);


    if (email && pdfLink) {
      try {
        var fileId = pdfLink.match(/[-\w]{25,}/);
        if (fileId) {
          var pdfFile = DriveApp.getFileById(fileId[0]).getAs(MimeType.PDF);
          MailApp.sendEmail({
            to: email,
            subject: baseInvoiceName,
            body: "Please find attached your invoice.",
            attachments: [pdfFile]
          });
          Logger.log("Email sent to: " + email);
          copiedSheet.getRange(i + 1, headers.indexOf("status") + 1).setValue("Sent");
          masterSheet.getRange(i + 1, headers.indexOf("status") + 1).setValue("Sent");
        } else {
          Logger.log("Invalid PDF link: " + pdfLink);
          copiedSheet.getRange(i + 1, headers.indexOf("status") + 1).setValue("Invalid link");
          masterSheet.getRange(i + 1, headers.indexOf("status") + 1).setValue("Invalid link");
        }
      } catch (e) {
        Logger.log("Failed to send email to: " + email + " with error: " + e.message);
        copiedSheet.getRange(i + 1, headers.indexOf("status") + 1).setValue("Failed to send");
        masterSheet.getRange(i + 1, headers.indexOf("status") + 1).setValue("Failed to send");
      }
    } else {
      Logger.log("No email address or PDF link found for row: " + (i + 1));
      copiedSheet.getRange(i + 1, headers.indexOf("status") + 1).setValue("No email found");
      masterSheet.getRange(i + 1, headers.indexOf("status") + 1).setValue("No email found");
    }
  }


  SpreadsheetApp.getUi().alert("PDFs successfully sent.");
}


function createMonthlySheetCopy() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var masterSheet = ss.getSheetByName("master-clients");
  if (!masterSheet) {
    throw new Error('Sheet "master-clients" not found');
  }
  var currentDate = new Date();
  var currentMonth = new Intl.DateTimeFormat('en-US', { month: 'long' }).format(currentDate);
  var lastTwoDigitsOfYear = String(currentDate.getFullYear()).slice(-2);
  var newSheetName = currentMonth + " " + lastTwoDigitsOfYear + " Invoices";
  var existingSheet = ss.getSheetByName(newSheetName);
  if (existingSheet) {
    ss.deleteSheet(existingSheet);
  }
  var copiedSheet = masterSheet.copyTo(ss).setName(newSheetName);
  return copiedSheet;
}

Scroll to Top