It worked well on previous lines but when a script sees that the value is negative, expects it to be positive it then breaks the entire script, meanwhile, before the new Google Ads scripts experience it just logged in the results that the new campaign budget is {{newBudget}}, returned value of course was negative budget but it left the previously set budget and continued to run through other campaigns. At the current scripts version it does not.
Instead of breaking the script, continue with next campaign in the list.
Later on, when such an error occurs, I will attempt to create a custom function for it, I'm not yet sure what I want, if I want that campaign to be paused because it is obviously overspending or I will want to get notified by email when such an error happens.
I am not that good with scripts to build this out myself as quickly as I need it.
var SPREADSHEET_URL = '
https://docs.google.com/spreadsheets/d/1-qwXrn3Z7Y3uVbnNEbLVbUlLNYVKInAlfXhBjtxV9aE/edit#gid=0';
// Please fix the following variables if you need to reformat the spreadsheet
// column numbers of each config column. Column A in your spreadsheet has
// column number of 1, B has number of 2, etc.
var COLUMN = {
accountId: 3,
campaignId: 5,
startDate: 7,
endDate: 8,
totalBudget: 9,
results: 10
};
// Actual config (without header and margin) starts from this row
var CONFIG_START_ROW = 4;
function main() {
// Uncomment the following function to test your budget strategy function
testBudgetStrategy(calculateBudgetEvenly, 10, 500);
setNewBudget(calculateBudgetEvenly);
}
// Core logic for calculating and setting campaign daily budget
function setNewBudget(budgetFunc) {
Logger.log('Using spreadsheet - %s.', SPREADSHEET_URL);
var spreadsheet = validateAndGetSpreadsheet(SPREADSHEET_URL);
spreadsheet.setSpreadsheetTimeZone(AdsApp.currentAccount().getTimeZone());
var sheet = spreadsheet.getSheets()[0];
var endRow = sheet.getLastRow();
var mccAccount = AdsApp.currentAccount();
sheet.getRange(1, 6).setValue(mccAccount.getCustomerId());
for (var i = CONFIG_START_ROW; i <= endRow; i++) {
Logger.log('Working on the line %s', i);
var accountId = sheet.getRange(i, COLUMN.accountId).getValue();
var campaignId = sheet.getRange(i, COLUMN.campaignId).getValue();
var startDate = new Date(sheet.getRange(i, COLUMN.startDate).getValue());
var endDate = new Date(sheet.getRange(i, COLUMN.endDate).getValue());
var totalBudget = sheet.getRange(i, COLUMN.totalBudget).getValue();
var resultCell = sheet.getRange(i, COLUMN.results);
var accountIter = AdsManagerApp.accounts().withIds([accountId]).get();
if (!accountIter.hasNext()) {
resultCell.setValue('Unrecognized Acc');
continue;
}
var account = accountIter.next();
AdsManagerApp.select(account);
var campaign = getCampaign(campaignId);
if (!campaign) {
resultCell.setValue('Unrecognized Camp.');
continue;
}
var today = new Date();
if (today < startDate) {
resultCell.setValue('Budget did not start');
continue;
}
if (today > endDate) {
resultCell.setValue('Budget ended');
continue;
}
var costSoFar = campaign.getStatsFor(
getDateStringInTimeZone('yyyyMMdd', startDate),
getDateStringInTimeZone('yyyyMMdd', endDate)).getCost();
var daysSoFar = datediff(startDate, today);
var totalDays = datediff(startDate, endDate);
var newBudget = budgetFunc(costSoFar, totalBudget, daysSoFar, totalDays);
campaign.getBudget().setAmount(newBudget);
Logger.log('AccountId=%s, campaignId=%s, StartDate=%s, EndDate=%s, ' +
'CostSoFar=%s, DaysSoFar=%s, TotalDays=%s, NewBudget=%s',
accountId, campaignId, startDate, endDate,
costSoFar, daysSoFar, totalDays, newBudget);
resultCell.setValue('New Daily Budget ' + newBudget);
}
// update "Last execution" timestamp
sheet.getRange(1, 5).setValue(today);
AdsManagerApp.select(mccAccount);
}
// One calculation logic that distributes remaining budget evenly
function calculateBudgetEvenly(costSoFar, totalBudget, daysSoFar, totalDays) {
var daysRemaining = totalDays - daysSoFar;
var budgetRemaining = totalBudget - costSoFar;
if (daysRemaining <= 0) {
return budgetRemaining;
} else {
return budgetRemaining / daysRemaining;
}
}
// One calculation logic that distributes remaining budget in a weighted manner
function calculateBudgetWeighted(costSoFar, totalBudget, daysSoFar, totalDays) {
var daysRemaining = totalDays - daysSoFar;
var budgetRemaining = totalBudget - costSoFar;
if (daysRemaining <= 0) {
return budgetRemaining;
} else {
return budgetRemaining / (2 * daysRemaining - 1);
}
}
// Test function to verify budget calculation logic
function testBudgetStrategy(budgetFunc, totalDays, totalBudget) {
var daysSoFar = 0;
var costSoFar = 0;
while (daysSoFar <= totalDays + 2) {
var newBudget = budgetFunc(costSoFar, totalBudget, daysSoFar, totalDays);
Logger.log('Day %s of %s, new budget %s, cost so far %s',
daysSoFar + 1, totalDays, newBudget, costSoFar);
costSoFar += newBudget;
daysSoFar += 1;
}
}
// Return number of days between two dates, rounded up to nearest whole day.
function datediff(from, to) {
var millisPerDay = 1000 * 60 * 60 * 24;
return Math.ceil((to - from) / millisPerDay);
}
// Produces a formatted string representing a given date in a given time zone.
function getDateStringInTimeZone(format, date, timeZone) {
date = date || new Date();
timeZone = timeZone || AdsApp.currentAccount().getTimeZone();
return Utilities.formatDate(date, timeZone, format);
}
/**
* Finds a campaign by name, whether it is a regular, video, or shopping
* campaign, by trying all in sequence until it finds one.
*
* @param {string} campaigId The campaign id to find.
* @return {Object} The campaign found, or null if none was found.
*/
function getCampaign(campaignId) {
var selectors = [AdsApp.campaigns(), AdsApp.videoCampaigns(),
AdsApp.shoppingCampaigns()];
for(var i = 0; i < selectors.length; i++) {
var campaignIter = selectors[i].
withCondition('CampaignId = "' + campaignId + '"').
get();
if (campaignIter.hasNext()) {
return campaignIter.next();
}
}
return null;
}
/**
* Validates the provided spreadsheet URL to make sure that it's set up
* properly. Throws a descriptive error message if validation fails.
*
* @param {string} spreadsheeturl The URL of the spreadsheet to open.
* @return {Spreadsheet} The spreadsheet object itself, fetched from the URL.
* @throws {Error} If the spreadsheet URL hasn't been set
*/
function validateAndGetSpreadsheet(spreadsheeturl) {
if (spreadsheeturl == 'YOUR_SPREADSHEET_URL') {
throw new Error('Provide Valid URL' +
' Template URL.');
}
return SpreadsheetApp.openByUrl(spreadsheeturl);
}