import { getCellValueByColumnName } from "./sheetUtils" import { Product } from "./Product" import { Shop } from "./shopifyApi" // --- Constants --- const BATCH_INTERVAL_MS = 30 * 1000 // 30 seconds const LOCK_TIMEOUT_MS = 10 * 1000 // 10 seconds for lock acquisition const CACHE_KEY_EDITS = "pendingEdits" const CACHE_KEY_LAST_EDIT_TIME = "lastEditTime" const SCRIPT_PROPERTY_TRIGGER_SCHEDULED = "batchTriggerScheduled" export function onEditQueue(e) { const sheet = e.source.getActiveSheet() if (sheet.getName() !== "product_inventory") { console.log("skipping edit on sheet " + sheet.getName()) return } const range = e.range const row = range.getRow() const sku = getCellValueByColumnName(sheet, "sku", row) if (!sku) { console.log("No SKU found for row " + row) return } // Make sure SKU conforms to expected patterns if (sku.match(`\\?`) || sku.match(`n$`)) { console.log("SKU is a placeholder ('?' or 'n...'), skipping batching.") return } // Acquire a user lock to prevent multiple onEdit calls from clashing const documentLock = LockService.getDocumentLock() try { documentLock.waitLock(LOCK_TIMEOUT_MS) const scriptProperties = PropertiesService.getScriptProperties() // Shared cache for all users // 1. Accumulate edits in cache let pendingEdits = [] try { pendingEdits = JSON.parse( scriptProperties.getProperty(CACHE_KEY_EDITS) || "[]" ) } catch (e) { console.log("Cache corruption: " + e.message) scriptProperties.setProperty(CACHE_KEY_EDITS, "[]") } const existingIndex = pendingEdits.findIndex((item) => item.sku === sku) if (existingIndex !== -1) { console.log("New edit on queued SKU '"+sku+"', resetting timer...") pendingEdits[existingIndex].timestamp = Date.now() } else { console.log("New SKU '"+sku+"' added to queue.") pendingEdits.push({ sku, timestamp: Date.now() }) } scriptProperties.setProperty(CACHE_KEY_EDITS, JSON.stringify(pendingEdits)) } catch (error) { console.log( "Error in onEdit (lock acquisition or cache operation): " + error.message ) } finally { documentLock.releaseLock() } } export function processBatchedEdits() { const scriptLock = LockService.getScriptLock() // Use script lock for the processing function try { scriptLock.waitLock(LOCK_TIMEOUT_MS) const scriptProperties = PropertiesService.getScriptProperties() let pendingEdits = [] try { const pendingEditsStr = scriptProperties.getProperty(CACHE_KEY_EDITS) pendingEdits = pendingEditsStr ? JSON.parse(pendingEditsStr) : [] } catch (e) { console.log("Cache corruption: " + e.message) scriptProperties.setProperty(CACHE_KEY_EDITS, "[]") } console.log(`Total SKUs in queue: ${pendingEdits.length}`) const now = Date.now() const toProcess = pendingEdits.filter( (edit) => now - edit.timestamp > BATCH_INTERVAL_MS ) if (toProcess.length > 0) { let shop = new Shop() console.log(`Processing ${toProcess.length} SKUs...`) toProcess.forEach((edit) => { console.log( `Processing SKU ${edit.sku}, Timestamp: ${new Date(edit.timestamp)}` ) // Make sure SKU conforms to expected patterns if (!edit.sku.match(/^\w+-\d{4}$/)) { console.log(`SKU ${edit.sku} is not valid, skipping processing.`) return } let p = new Product(edit.sku) p.UpdateShopifyProduct(shop) }) pendingEdits = pendingEdits.filter( edit => !toProcess.some(p => p.sku === edit.sku) ); scriptProperties.setProperty(CACHE_KEY_EDITS, JSON.stringify(pendingEdits)); console.log(`Processed ${toProcess.length} edits.`) } else { console.log("No pending edits to process.") } } catch (error) { console.log( "Error in processBatchedEdits (lock acquisition or processing): " + error.message ) } finally { scriptLock.releaseLock() } }