Automated Invoicing from Spreadsheets

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;
}