/** * Container Tracking — write the latest results back into YOUR Google Sheet (Google Apps Script, no OAuth app). * * The sheet you give the Actor as `numbersSheetUrl` stays your list; this script fills five columns next to each * number with the newest status, ETA (UTC), last free day, demurrage at risk and the note of the Actor's last run. * * Set up once (≈ 2 minutes): * 1. In your sheet: Extensions → Apps Script, paste this whole file, Save. * 2. Project Settings → Script properties → add APIFY_TOKEN = your token (console.apify.com → Settings → API & * Integrations). Optional: TASK_ID = the saved task of this monitor (OUTPUT.scheduleTaskUrl, the id after * /tasks/) — without it the Actor's last run on your account is read. * 3. Run `writeBack` once (allow the permissions), then Triggers → Add trigger → writeBack, time-driven, every day * one hour after your schedule. * * Rows are matched on the container / B/L / booking number in column A (header `number`); the script only writes * the result columns, never your own ones. */ const ACTOR = 'rich_minds~multi-carrier-tracking-ai'; const RESULT_COLUMNS = ['status', 'etaUtc', 'lastFreeDay', 'demurrageAtRiskUsd', 'note', 'checkedAt']; function writeBack() { const props = PropertiesService.getScriptProperties(); const token = props.getProperty('APIFY_TOKEN'); if (!token) throw new Error('Add the script property APIFY_TOKEN first (Project Settings → Script properties).'); const task = props.getProperty('TASK_ID'); const base = task ? `https://api.apify.com/v2/actor-tasks/${task}` : `https://api.apify.com/v2/acts/${ACTOR}`; const url = `${base}/runs/last/dataset/items?status=SUCCEEDED&clean=true&format=json`; const resp = UrlFetchApp.fetch(url, { headers: { Authorization: `Bearer ${token}` }, muteHttpExceptions: true }); if (resp.getResponseCode() !== 200) throw new Error(`Apify answered ${resp.getResponseCode()}: ${resp.getContentText()}`); const items = JSON.parse(resp.getContentText()); // the newest row per number (a box may come back as several updates) const latest = {}; items.forEach((item) => { const number = String(item.trackingNumber || '').toUpperCase(); if (number && (!latest[number] || String(item.checkedAt) > String(latest[number].checkedAt))) latest[number] = item; }); const sheet = SpreadsheetApp.getActiveSheet(); const values = sheet.getDataRange().getValues(); const header = values[0].map((h) => String(h).trim()); // the result columns go after your last column the first time, then stay where they are const col = {}; RESULT_COLUMNS.forEach((name) => { let i = header.indexOf(name); if (i < 0) { header.push(name); i = header.length - 1; sheet.getRange(1, i + 1).setValue(name); } col[name] = i + 1; }); let written = 0; for (let r = 1; r < values.length; r++) { const number = String(values[r][0] || '').split(',')[0].trim().toUpperCase(); const item = latest[number]; if (!item) continue; // unchanged since the last run, or not tracked — the row keeps its last values RESULT_COLUMNS.forEach((name) => sheet.getRange(r + 1, col[name]).setValue(item[name] == null ? '' : item[name])); written++; } Logger.log(`${written} row(s) updated from ${items.length} result(s).`); }