Guest User

CycleCountSheetEmailer

a guest
Jul 20th, 2025
26
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
JavaScript 7.56 KB | Source Code | 0 0
  1. function onOpen() {
  2.   const ui = SpreadsheetApp.getUi();
  3.   ui.createMenu("Inventory Tools")
  4.     .addItem("Run Daily Count Now", "dailyInventoryCount")
  5.     .addItem("Reset Count History", "resetCountHistory")
  6.     .addToUi();
  7. }
  8.  
  9. function dailyInventoryCount() {
  10.   const ss = SpreadsheetApp.getActiveSpreadsheet();
  11.   createInstructionsSheet(ss);
  12.   const emailSheet = getOrCreateSheet(ss, "Emails");
  13.   const historySheet = getOrCreateSheet(ss, "CountHistory");
  14.   const sendNowSheet = getOrCreateSheet(ss, "SendNow");
  15.   const catalogSheetName = "AutoCatalogUpdate";
  16.  
  17.   const emailDataRaw = emailSheet.getRange("A:A").getValues().flat().filter(e => e);
  18.  
  19.   if (emailDataRaw.length < 1) {
  20.     SpreadsheetApp.getUi().alert("Emails sheet is missing Source URL.");
  21.     return;
  22.   }
  23.  
  24.   const sourceUrl = emailDataRaw[0];
  25.   const sourceSheetName = emailDataRaw[1] || ""; // optional sheet name
  26.   const emailRecipients = emailDataRaw.slice(2);
  27.  
  28.   if (emailRecipients.length === 0) {
  29.     SpreadsheetApp.getUi().alert("No email recipients found in Emails sheet.");
  30.     return;
  31.   }
  32.  
  33.   const sourceSs = SpreadsheetApp.openByUrl(sourceUrl);
  34.  
  35.   let sourceCatalogSheet;
  36.   if (sourceSheetName) {
  37.     sourceCatalogSheet = sourceSs.getSheetByName(sourceSheetName);
  38.     if (!sourceCatalogSheet) {
  39.       SpreadsheetApp.getUi().alert(`Sheet named '${sourceSheetName}' not found in source spreadsheet.`);
  40.       return;
  41.     }
  42.   } else {
  43.     sourceCatalogSheet = sourceSs.getSheets().find(sheet => !sheet.isSheetHidden());
  44.     if (!sourceCatalogSheet) {
  45.       SpreadsheetApp.getUi().alert("No visible sheet found in source spreadsheet.");
  46.       return;
  47.     }
  48.   }
  49.  
  50.   // Refresh AutoCatalogUpdate sheet in current spreadsheet
  51.   let catalogSheet = ss.getSheetByName(catalogSheetName);
  52.   if (!catalogSheet) {
  53.     catalogSheet = ss.insertSheet(catalogSheetName);
  54.   } else {
  55.     catalogSheet.clearContents();
  56.   }
  57.   const sourceData = sourceCatalogSheet.getDataRange().getValues();
  58.   catalogSheet.getRange(1, 1, sourceData.length, sourceData[0].length).setValues(sourceData);
  59.  
  60.   const catalogData = catalogSheet.getDataRange().getValues();
  61.   const historyData = historySheet.getDataRange().getValues();
  62.   const sendNowData = sendNowSheet.getRange("A:A").getValues().flat().filter(e => e);
  63.  
  64.   const today = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "yyyy-MM-dd");
  65.  
  66.   // Build history map: ID → LastCountedDate
  67.   const historyMap = {};
  68.   for (let i = 1; i < historyData.length; i++) {
  69.     historyMap[historyData[i][0]] = historyData[i][1];
  70.   }
  71.  
  72.   const itemsToCount = [];
  73.  
  74.   // If SendNow sheet has IDs, use those
  75.   if (sendNowData.length > 0) {
  76.     for (let i = 1; i < catalogData.length; i++) {
  77.       const id = catalogData[i][0];
  78.       if (!id) continue;
  79.       if (sendNowData.includes(id)) {
  80.         const imageUrl = catalogData[i][40] || ""; // AO = index 40
  81.         const name = catalogData[i][2] || "";
  82.         const variation = catalogData[i][5] || "";
  83.         const price = catalogData[i][6] || "";
  84.         const upc = catalogData[i][7] || "";
  85.         const category = catalogData[i][24] || "";
  86.         const inventory = catalogData[i][38] || "";
  87.  
  88.         const itemDescription = `
  89. <b>${name}${variation ? ' - ' + variation : ''}</b><br>
  90. Price: $${price}<br>
  91. UPC: ${upc}<br>
  92. Category: ${category}<br>
  93. Inventory: ${inventory}
  94. `;
  95.         itemsToCount.push({ id, imageUrl, itemDescription });
  96.       }
  97.     }
  98.   }
  99.  
  100.   // If no SendNow, fallback to uncounted up to 20
  101.   if (itemsToCount.length === 0) {
  102.     for (let i = 1; i < catalogData.length; i++) {
  103.       const id = catalogData[i][0];
  104.       if (!id) continue;
  105.  
  106.       if (!historyMap[id]) {
  107.         const imageUrl = catalogData[i][40] || "";
  108.         const name = catalogData[i][2] || "";
  109.         const variation = catalogData[i][5] || "";
  110.         const price = catalogData[i][6] || "";
  111.         const upc = catalogData[i][7] || "";
  112.         const category = catalogData[i][24] || "";
  113.         const inventory = catalogData[i][38] || "";
  114.  
  115.         const itemDescription = `
  116. <b>${name}${variation ? ' - ' + variation : ''}</b><br>
  117. Price: $${price}<br>
  118. UPC: ${upc}<br>
  119. Category: ${category}<br>
  120. Inventory: ${inventory}
  121. `;
  122.         itemsToCount.push({ id, imageUrl, itemDescription });
  123.         if (itemsToCount.length >= 20) break;
  124.       }
  125.     }
  126.   }
  127.  
  128.   if (itemsToCount.length === 0) {
  129.     SpreadsheetApp.getUi().alert("No items to count today.");
  130.     return;
  131.   }
  132.  
  133.   const emailHtml = `
  134. <p>Hello Team,</p>
  135. <p>Here are the next ${itemsToCount.length} items to count today:</p>
  136. <table border="1" cellpadding="4" cellspacing="0">
  137. <tr><th>#</th><th>Image</th><th>Description</th></tr>
  138. ${itemsToCount.map((item, index) => `
  139. <tr>
  140. <td>${index + 1}</td>
  141. <td><img src="${item.imageUrl}" width="50" /></td>
  142. <td>${item.itemDescription}</td>
  143. </tr>`).join("")}
  144. </table>
  145. <p>Please mark these as counted after you finish.</p>
  146. <p>Thank you!</p>`;
  147.  
  148.   const subject = `Daily Inventory Count - ${today}`;
  149.  
  150.   emailRecipients.forEach(email => {
  151.     MailApp.sendEmail({
  152.       to: email,
  153.       subject: subject,
  154.       htmlBody: emailHtml
  155.     });
  156.   });
  157.  
  158.   // Update history map
  159.   for (const item of itemsToCount) {
  160.     historyMap[item.id] = today;
  161.   }
  162.  
  163.   const newHistory = [["ID", "LastCountedDate"]];
  164.   for (const [id, date] of Object.entries(historyMap)) {
  165.     newHistory.push([id, date]);
  166.   }
  167.  
  168.   historySheet.clearContents();
  169.   historySheet.getRange(1, 1, newHistory.length, newHistory[0].length).setValues(newHistory);
  170.  
  171.   // Clear SendNow sheet
  172.   sendNowSheet.clearContents();
  173.  
  174.   SpreadsheetApp.getUi().alert(`Emailed ${emailRecipients.length} recipients with ${itemsToCount.length} items.`);
  175. }
  176.  
  177. function resetCountHistory() {
  178.   const ss = SpreadsheetApp.getActiveSpreadsheet();
  179.   createInstructionsSheet(ss);
  180.   const historySheet = getOrCreateSheet(ss, "CountHistory");
  181.   const catalogSheet = getOrCreateSheet(ss, "AutoCatalogUpdate");
  182.  
  183.   const catalogData = catalogSheet.getDataRange().getValues();
  184.  
  185.   const newHistory = [["ID", "LastCountedDate"]];
  186.   for (let i = 1; i < catalogData.length; i++) {
  187.     const id = catalogData[i][0];
  188.     if (id) {
  189.       newHistory.push([id, ""]);
  190.     }
  191.   }
  192.  
  193.   historySheet.clearContents();
  194.   historySheet.getRange(1, 1, newHistory.length, newHistory[0].length).setValues(newHistory);
  195.  
  196.   SpreadsheetApp.getUi().alert("Count history has been reset.");
  197. }
  198.  
  199. function createTrigger() {
  200.   ScriptApp.newTrigger("dailyInventoryCount")
  201.     .timeBased()
  202.     .everyDays(1)
  203.     .atHour(8) // adjust if desired
  204.     .create();
  205. }
  206.  
  207. // Utility: get or create sheet
  208. function getOrCreateSheet(ss, sheetName) {
  209.   let sheet = ss.getSheetByName(sheetName);
  210.   if (!sheet) {
  211.     sheet = ss.insertSheet(sheetName);
  212.   }
  213.   return sheet;
  214. }
  215.  
  216. // Utility: create Instructions sheet if missing
  217. function createInstructionsSheet(ss) {
  218.   let sheet = ss.getSheetByName("Instructions");
  219.   if (!sheet) {
  220.     sheet = ss.insertSheet("Instructions");
  221.     const data = [
  222.       "📋 Inventory Count Sheet - Instructions",
  223.       "",
  224.       "1️⃣ Fill in the `Emails` sheet:",
  225.       "A1: URL of source catalog",
  226.       "A2: Optional: sheet name (leave blank for first sheet)",
  227.       "A3+: Email addresses of recipients",
  228.       "",
  229.       "2️⃣ Use the `SendNow` sheet to specify IDs you want counted next",
  230.       "Leave `SendNow` empty to default to next 20 uncounted",
  231.       "",
  232.       "3️⃣ Use the `Inventory Tools` menu to:",
  233.       "- Run daily count now",
  234.       "- Reset count history"
  235.     ];
  236.     sheet.getRange(1, 1, data.length, 1).setValues(
  237.       data.map(line => [line])
  238.     );
  239.   }
  240. }
  241.  
Advertisement
Add Comment
Please, Sign In to add comment